@thinkdata/mcp-server 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -7397,10 +7397,6 @@ var init_src = __esm({
7397
7397
  }
7398
7398
  });
7399
7399
 
7400
- // src/index.ts
7401
- import { readFileSync, existsSync } from "node:fs";
7402
- import { resolve } from "node:path";
7403
-
7404
7400
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
7405
7401
  var external_exports = {};
7406
7402
  __export(external_exports, {
@@ -18899,13 +18895,13 @@ var zodToJsonSchema = (schema, options) => {
18899
18895
  }, true) ?? parseAnyDef(refs)
18900
18896
  }), {}) : void 0;
18901
18897
  const name = typeof options === "string" ? options : options?.nameStrategy === "title" ? void 0 : options?.name;
18902
- const main2 = parseDef(schema._def, name === void 0 ? refs : {
18898
+ const main = parseDef(schema._def, name === void 0 ? refs : {
18903
18899
  ...refs,
18904
18900
  currentPath: [...refs.basePath, refs.definitionPath, name]
18905
18901
  }, false) ?? parseAnyDef(refs);
18906
18902
  const title = typeof options === "object" && options.name !== void 0 && options.nameStrategy === "title" ? options.name : void 0;
18907
18903
  if (title !== void 0) {
18908
- main2.title = title;
18904
+ main.title = title;
18909
18905
  }
18910
18906
  if (refs.flags.hasReferencedOpenAiAnyType) {
18911
18907
  if (!definitions) {
@@ -18926,9 +18922,9 @@ var zodToJsonSchema = (schema, options) => {
18926
18922
  }
18927
18923
  }
18928
18924
  const combined = name === void 0 ? definitions ? {
18929
- ...main2,
18925
+ ...main,
18930
18926
  [refs.definitionPath]: definitions
18931
- } : main2 : {
18927
+ } : main : {
18932
18928
  $ref: [
18933
18929
  ...refs.$refStrategy === "relative" ? [] : refs.basePath,
18934
18930
  refs.definitionPath,
@@ -18936,7 +18932,7 @@ var zodToJsonSchema = (schema, options) => {
18936
18932
  ].join("/"),
18937
18933
  [refs.definitionPath]: {
18938
18934
  ...definitions,
18939
- [name]: main2
18935
+ [name]: main
18940
18936
  }
18941
18937
  };
18942
18938
  if (refs.target === "jsonSchema7") {
@@ -21613,957 +21609,824 @@ var StdioServerTransport = class {
21613
21609
  }
21614
21610
  };
21615
21611
 
21616
- // src/resources.ts
21612
+ // ../cli/dist/commands/mcp.js
21613
+ import { existsSync, readFileSync } from "node:fs";
21614
+ import { resolve } from "node:path";
21615
+ import { fileURLToPath } from "node:url";
21616
+ import { dirname } from "node:path";
21617
+
21618
+ // ../cli/dist/mcp/client.js
21619
+ var ThinkERDClient = class {
21620
+ baseUrl;
21621
+ headers;
21622
+ constructor(apiUrl, token) {
21623
+ this.baseUrl = apiUrl.replace(/\/+$/, "");
21624
+ this.headers = {
21625
+ "Authorization": `Bearer ${token}`,
21626
+ "Content-Type": "application/json"
21627
+ };
21628
+ }
21629
+ async request(path) {
21630
+ const url = `${this.baseUrl}${path}`;
21631
+ const res = await fetch(url, { headers: this.headers });
21632
+ if (!res.ok) {
21633
+ const body = await res.text().catch(() => "");
21634
+ throw new Error(`ThinkERD API error ${res.status}: ${body || res.statusText}`);
21635
+ }
21636
+ const contentType = res.headers.get("content-type") || "";
21637
+ if (contentType.includes("text/plain")) {
21638
+ return res.text();
21639
+ }
21640
+ return res.json();
21641
+ }
21642
+ // ─── Diagrams ───
21643
+ /** 사용자의 모든 다이어그램 목록 조회 */
21644
+ async listDiagrams() {
21645
+ return this.request("/api/mcp/diagrams");
21646
+ }
21647
+ /** 다이어그램 상세 (엔터티 + 관계 포함) */
21648
+ async getDiagram(diagramId) {
21649
+ return this.request(`/api/mcp/diagrams/${diagramId}`);
21650
+ }
21651
+ // ─── Entities ───
21652
+ /** 단일 엔터티 상세 조회 */
21653
+ async getEntity(diagramId, entityId) {
21654
+ return this.request(`/api/mcp/diagrams/${diagramId}/entities/${entityId}`);
21655
+ }
21656
+ /** 엔터티 이름으로 검색 */
21657
+ async searchEntities(diagramId, query) {
21658
+ return this.request(`/api/mcp/diagrams/${diagramId}/entities?q=${encodeURIComponent(query)}`);
21659
+ }
21660
+ // ─── DDL ───
21661
+ /** 다이어그램의 DDL 생성 */
21662
+ async generateDDL(diagramId, dialect) {
21663
+ const d = dialect ? `?dialect=${encodeURIComponent(dialect)}` : "";
21664
+ return this.request(`/api/mcp/diagrams/${diagramId}/ddl${d}`);
21665
+ }
21666
+ // ─── Dictionary ───
21667
+ /** 표준 사전 단어 목록 */
21668
+ async getDictionary(projectId) {
21669
+ return this.request(`/api/mcp/projects/${projectId}/dictionary`);
21670
+ }
21671
+ // ─── Drafts ───
21672
+ /** 스키마 변경 제안 (Draft 생성) */
21673
+ async proposeSchemaChange(diagramId, data) {
21674
+ return this.postRequest(`/api/mcp/diagrams/${diagramId}/drafts`, data);
21675
+ }
21676
+ // ─── Sync / Push ───
21677
+ async postRequest(path, body) {
21678
+ const url = `${this.baseUrl}${path}`;
21679
+ const res = await fetch(url, {
21680
+ method: "POST",
21681
+ headers: this.headers,
21682
+ body: JSON.stringify(body)
21683
+ });
21684
+ if (!res.ok) {
21685
+ const text = await res.text().catch(() => "");
21686
+ throw new Error(`ThinkERD API error ${res.status}: ${text || res.statusText}`);
21687
+ }
21688
+ return res.json();
21689
+ }
21690
+ // ─── Context Provider (Project-level Ontology) ───
21691
+ /** 자연어 질문 → 관련 엔터티 컨텍스트 (Graph RAG) */
21692
+ async getProjectContext(projectId, question) {
21693
+ return this.postRequest(`/api/mcp/projects/${projectId}/context`, { question });
21694
+ }
21695
+ /** 두 엔터티 간 JOIN 경로 탐색 */
21696
+ async findJoinPath(projectId, from, to) {
21697
+ return this.postRequest(`/api/mcp/projects/${projectId}/join-path`, { from, to });
21698
+ }
21699
+ /** 프로젝트 전체 스키마 요약 */
21700
+ async getProjectSummary(projectId) {
21701
+ return this.request(`/api/mcp/projects/${projectId}/summary`);
21702
+ }
21703
+ /** 메타데이터 풍부도 품질 보고 */
21704
+ async checkMetadataQuality(projectId) {
21705
+ return this.request(`/api/mcp/projects/${projectId}/quality`);
21706
+ }
21707
+ /** SPARQL 직접 실행 */
21708
+ async queryOntology(projectId, query) {
21709
+ return this.postRequest(`/api/ai/ontology/${projectId}/sparql`, { query });
21710
+ }
21711
+ };
21712
+
21713
+ // ../cli/dist/mcp/resources.js
21617
21714
  function registerResources(server, client) {
21618
- server.resource(
21619
- "diagrams",
21620
- "thinkerd://diagrams",
21621
- {
21622
- description: "ThinkERD \uD504\uB85C\uC81D\uD2B8\uC758 \uBAA8\uB4E0 \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
21623
- },
21624
- async () => {
21625
- const diagrams = await client.listDiagrams();
21715
+ server.resource("diagrams", "thinkerd://diagrams", {
21716
+ description: "ThinkERD \uD504\uB85C\uC81D\uD2B8\uC758 \uBAA8\uB4E0 \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
21717
+ }, async () => {
21718
+ const diagrams = await client.listDiagrams();
21719
+ return {
21720
+ contents: [{
21721
+ uri: "thinkerd://diagrams",
21722
+ mimeType: "application/json",
21723
+ text: JSON.stringify(diagrams, null, 2)
21724
+ }]
21725
+ };
21726
+ });
21727
+ server.resource("diagram-schema", "thinkerd://diagrams/{diagramId}/schema", {
21728
+ description: "\uD2B9\uC815 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC804\uCCB4 \uC2A4\uD0A4\uB9C8(\uC5D4\uD130\uD2F0, \uCEEC\uB7FC, \uAD00\uACC4)\uB97C JSON\uC73C\uB85C \uBC18\uD658\uD569\uB2C8\uB2E4."
21729
+ }, async (uri) => {
21730
+ const diagramId = extractParam(uri.href, "diagrams");
21731
+ const data = await client.getDiagram(diagramId);
21732
+ return {
21733
+ contents: [{
21734
+ uri: uri.href,
21735
+ mimeType: "application/json",
21736
+ text: JSON.stringify(data, null, 2)
21737
+ }]
21738
+ };
21739
+ });
21740
+ server.resource("entity-detail", "thinkerd://diagrams/{diagramId}/entities/{entityId}", {
21741
+ description: "\uD2B9\uC815 \uC5D4\uD130\uD2F0\uC758 \uB17C\uB9AC\uBA85, \uBB3C\uB9AC\uBA85, \uCEEC\uB7FC \uC0C1\uC138(\uD0C0\uC785, PK/FK, Nullable \uB4F1)\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4."
21742
+ }, async (uri) => {
21743
+ const parts = uri.href.split("/");
21744
+ const entitiesIdx = parts.indexOf("entities");
21745
+ const diagramsIdx = parts.indexOf("diagrams");
21746
+ const diagramId = parts[diagramsIdx + 1];
21747
+ const entityId = parts[entitiesIdx + 1];
21748
+ const entity = await client.getEntity(diagramId, entityId);
21749
+ return {
21750
+ contents: [{
21751
+ uri: uri.href,
21752
+ mimeType: "application/json",
21753
+ text: JSON.stringify(entity, null, 2)
21754
+ }]
21755
+ };
21756
+ });
21757
+ server.resource("diagram-ddl", "thinkerd://diagrams/{diagramId}/ddl", {
21758
+ description: "\uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 DDL(CREATE TABLE \uAD6C\uBB38)\uC744 \uC0DD\uC131\uD558\uC5EC \uBC18\uD658\uD569\uB2C8\uB2E4."
21759
+ }, async (uri) => {
21760
+ const diagramId = extractParam(uri.href, "diagrams");
21761
+ const ddl = await client.generateDDL(diagramId);
21762
+ return {
21763
+ contents: [{
21764
+ uri: uri.href,
21765
+ mimeType: "text/plain",
21766
+ text: typeof ddl === "string" ? ddl : JSON.stringify(ddl)
21767
+ }]
21768
+ };
21769
+ });
21770
+ server.resource("dictionary", "thinkerd://projects/{projectId}/dictionary", {
21771
+ description: "\uD504\uB85C\uC81D\uD2B8\uC758 \uD45C\uC900 \uC0AC\uC804(\uB2E8\uC5B4, \uB3C4\uBA54\uC778, \uAE08\uCE59\uC5B4)\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
21772
+ }, async (uri) => {
21773
+ const projectId = extractParam(uri.href, "projects");
21774
+ const words = await client.getDictionary(projectId);
21775
+ return {
21776
+ contents: [{
21777
+ uri: uri.href,
21778
+ mimeType: "application/json",
21779
+ text: JSON.stringify(words, null, 2)
21780
+ }]
21781
+ };
21782
+ });
21783
+ server.resource("project-summary", "thinkerd://projects/{projectId}/summary", {
21784
+ description: "\uD504\uB85C\uC81D\uD2B8 \uC804\uCCB4 \uB370\uC774\uD130 \uBAA8\uB378\uC758 \uC694\uC57D(\uC5D4\uD130\uD2F0 \uC218, \uAD00\uACC4 \uC218, \uC8FC\uC81C\uC601\uC5ED \uAD6C\uC131, RDF \uD2B8\uB9AC\uD50C \uC218)\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
21785
+ }, async (uri) => {
21786
+ const projectId = extractParam(uri.href, "projects");
21787
+ const summary = await client.getProjectSummary(projectId);
21788
+ return {
21789
+ contents: [{
21790
+ uri: uri.href,
21791
+ mimeType: "application/json",
21792
+ text: JSON.stringify(summary, null, 2)
21793
+ }]
21794
+ };
21795
+ });
21796
+ server.resource("metadata-quality", "thinkerd://projects/{projectId}/quality", {
21797
+ description: "\uD504\uB85C\uC81D\uD2B8\uC758 \uBA54\uD0C0\uB370\uC774\uD130 \uD48D\uBD80\uB3C4(\uC124\uBA85, \uB3D9\uC758\uC5B4, \uC18D\uC131 \uC785\uB825\uB960)\uB97C \uBD84\uC11D\uD55C \uD488\uC9C8 \uBCF4\uACE0\uC11C\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4."
21798
+ }, async (uri) => {
21799
+ const projectId = extractParam(uri.href, "projects");
21800
+ const report = await client.checkMetadataQuality(projectId);
21801
+ return {
21802
+ contents: [{
21803
+ uri: uri.href,
21804
+ mimeType: "application/json",
21805
+ text: JSON.stringify(report, null, 2)
21806
+ }]
21807
+ };
21808
+ });
21809
+ }
21810
+ function extractParam(href, segment) {
21811
+ const parts = href.split("/");
21812
+ const idx = parts.indexOf(segment);
21813
+ if (idx === -1 || idx + 1 >= parts.length) {
21814
+ throw new Error(`Invalid URI: cannot extract '${segment}' parameter from ${href}`);
21815
+ }
21816
+ return parts[idx + 1];
21817
+ }
21818
+
21819
+ // ../cli/dist/mcp/tools.js
21820
+ function registerTools(server, client) {
21821
+ server.tool("get_entity", "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0\uC11C \uD2B9\uC815 \uC5D4\uD130\uD2F0\uC758 \uCEEC\uB7FC, \uD0C0\uC785, PK/FK \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.", {
21822
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21823
+ entityId: external_exports.string().describe("\uC5D4\uD130\uD2F0 ID")
21824
+ }, async ({ diagramId, entityId }) => {
21825
+ try {
21826
+ const entity = await client.getEntity(diagramId, entityId);
21626
21827
  return {
21627
- contents: [{
21628
- uri: "thinkerd://diagrams",
21629
- mimeType: "application/json",
21630
- text: JSON.stringify(diagrams, null, 2)
21828
+ content: [{
21829
+ type: "text",
21830
+ text: JSON.stringify(entity, null, 2)
21631
21831
  }]
21632
21832
  };
21633
- }
21634
- );
21635
- server.resource(
21636
- "diagram-schema",
21637
- "thinkerd://diagrams/{diagramId}/schema",
21638
- {
21639
- description: "\uD2B9\uC815 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC804\uCCB4 \uC2A4\uD0A4\uB9C8(\uC5D4\uD130\uD2F0, \uCEEC\uB7FC, \uAD00\uACC4)\uB97C JSON\uC73C\uB85C \uBC18\uD658\uD569\uB2C8\uB2E4."
21640
- },
21641
- async (uri) => {
21642
- const diagramId = extractParam(uri.href, "diagrams");
21643
- const data = await client.getDiagram(diagramId);
21833
+ } catch (err) {
21644
21834
  return {
21645
- contents: [{
21646
- uri: uri.href,
21647
- mimeType: "application/json",
21648
- text: JSON.stringify(data, null, 2)
21649
- }]
21835
+ content: [{
21836
+ type: "text",
21837
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
21838
+ }],
21839
+ isError: true
21650
21840
  };
21651
21841
  }
21652
- );
21653
- server.resource(
21654
- "entity-detail",
21655
- "thinkerd://diagrams/{diagramId}/entities/{entityId}",
21656
- {
21657
- description: "\uD2B9\uC815 \uC5D4\uD130\uD2F0\uC758 \uB17C\uB9AC\uBA85, \uBB3C\uB9AC\uBA85, \uCEEC\uB7FC \uC0C1\uC138(\uD0C0\uC785, PK/FK, Nullable \uB4F1)\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4."
21658
- },
21659
- async (uri) => {
21660
- const parts = uri.href.split("/");
21661
- const entitiesIdx = parts.indexOf("entities");
21662
- const diagramsIdx = parts.indexOf("diagrams");
21663
- const diagramId = parts[diagramsIdx + 1];
21664
- const entityId = parts[entitiesIdx + 1];
21665
- const entity = await client.getEntity(diagramId, entityId);
21842
+ });
21843
+ server.tool("search_entities", "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0\uC11C \uC774\uB984\uC73C\uB85C \uC5D4\uD130\uD2F0\uB97C \uAC80\uC0C9\uD569\uB2C8\uB2E4. \uB17C\uB9AC\uBA85/\uBB3C\uB9AC\uBA85 \uBAA8\uB450 \uAC80\uC0C9 \uAC00\uB2A5\uD569\uB2C8\uB2E4.", {
21844
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21845
+ query: external_exports.string().describe("\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC (\uC5D4\uD130\uD2F0 \uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)")
21846
+ }, async ({ diagramId, query }) => {
21847
+ try {
21848
+ const entities = await client.searchEntities(diagramId, query);
21849
+ const summary = entities.map((e) => ({
21850
+ id: e.id,
21851
+ name: e.name,
21852
+ tableName: e.tableName,
21853
+ role: e.entityRole,
21854
+ columnCount: e.columns.length,
21855
+ columns: e.columns.map((c) => `${c.name} (${c.type})${c.isPrimaryKey ? " [PK]" : ""}${c.isForeignKey ? " [FK]" : ""}`)
21856
+ }));
21666
21857
  return {
21667
- contents: [{
21668
- uri: uri.href,
21669
- mimeType: "application/json",
21670
- text: JSON.stringify(entity, null, 2)
21858
+ content: [{
21859
+ type: "text",
21860
+ text: JSON.stringify(summary, null, 2)
21671
21861
  }]
21672
21862
  };
21863
+ } catch (err) {
21864
+ return {
21865
+ content: [{
21866
+ type: "text",
21867
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
21868
+ }],
21869
+ isError: true
21870
+ };
21673
21871
  }
21674
- );
21675
- server.resource(
21676
- "diagram-ddl",
21677
- "thinkerd://diagrams/{diagramId}/ddl",
21678
- {
21679
- description: "\uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 DDL(CREATE TABLE \uAD6C\uBB38)\uC744 \uC0DD\uC131\uD558\uC5EC \uBC18\uD658\uD569\uB2C8\uB2E4."
21680
- },
21681
- async (uri) => {
21682
- const diagramId = extractParam(uri.href, "diagrams");
21683
- const ddl = await client.generateDDL(diagramId);
21872
+ });
21873
+ server.tool("generate_ddl", "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 DDL(CREATE TABLE \uAD6C\uBB38)\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4. PostgreSQL, MySQL, Oracle \uB4F1 \uB2E4\uC591\uD55C \uBC29\uC5B8\uC744 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", {
21874
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21875
+ dialect: external_exports.enum(["postgresql", "mysql", "oracle", "mssql", "sqlite"]).optional().default("postgresql").describe("SQL \uBC29\uC5B8 (\uAE30\uBCF8\uAC12: postgresql)")
21876
+ }, async ({ diagramId, dialect }) => {
21877
+ try {
21878
+ const ddl = await client.generateDDL(diagramId, dialect);
21684
21879
  return {
21685
- contents: [{
21686
- uri: uri.href,
21687
- mimeType: "text/plain",
21880
+ content: [{
21881
+ type: "text",
21688
21882
  text: typeof ddl === "string" ? ddl : JSON.stringify(ddl)
21689
21883
  }]
21690
21884
  };
21885
+ } catch (err) {
21886
+ return {
21887
+ content: [{
21888
+ type: "text",
21889
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
21890
+ }],
21891
+ isError: true
21892
+ };
21691
21893
  }
21692
- );
21693
- server.resource(
21694
- "dictionary",
21695
- "thinkerd://projects/{projectId}/dictionary",
21696
- {
21697
- description: "\uD504\uB85C\uC81D\uD2B8\uC758 \uD45C\uC900 \uC0AC\uC804(\uB2E8\uC5B4, \uB3C4\uBA54\uC778, \uAE08\uCE59\uC5B4)\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
21698
- },
21699
- async (uri) => {
21700
- const projectId = extractParam(uri.href, "projects");
21701
- const words = await client.getDictionary(projectId);
21894
+ });
21895
+ server.tool("validate_schema", "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC2A4\uD0A4\uB9C8\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4. \uB204\uB77D\uB41C PK, \uBBF8\uC5F0\uACB0 FK, \uBE44\uD45C\uC900 \uB124\uC774\uBC0D \uB4F1\uC744 \uC810\uAC80\uD569\uB2C8\uB2E4.", {
21896
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID")
21897
+ }, async ({ diagramId }) => {
21898
+ try {
21899
+ const data = await client.getDiagram(diagramId);
21900
+ const issues = [];
21901
+ for (const entity of data.entities) {
21902
+ const hasPK = entity.columns.some((c) => c.isPrimaryKey);
21903
+ if (!hasPK) {
21904
+ issues.push(`\u26A0\uFE0F [${entity.name}] PK(Primary Key)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`);
21905
+ }
21906
+ }
21907
+ for (const entity of data.entities) {
21908
+ if (entity.columns.length === 0) {
21909
+ issues.push(`\u26A0\uFE0F [${entity.name}] \uCEEC\uB7FC\uC774 \uD558\uB098\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4.`);
21910
+ }
21911
+ }
21912
+ const connectedIds = /* @__PURE__ */ new Set();
21913
+ for (const rel of data.relationships) {
21914
+ connectedIds.add(rel.sourceEntityId);
21915
+ connectedIds.add(rel.targetEntityId);
21916
+ }
21917
+ for (const entity of data.entities) {
21918
+ if (!connectedIds.has(entity.id) && data.entities.length > 1) {
21919
+ issues.push(`\u2139\uFE0F [${entity.name}] \uB2E4\uB978 \uC5D4\uD130\uD2F0\uC640 \uAD00\uACC4\uAC00 \uC5C6\uB294 \uACE0\uB9BD \uC5D4\uD130\uD2F0\uC785\uB2C8\uB2E4.`);
21920
+ }
21921
+ }
21922
+ const summary = issues.length === 0 ? `\u2705 \uC2A4\uD0A4\uB9C8 \uAC80\uC99D \uC644\uB8CC: ${data.entities.length}\uAC1C \uC5D4\uD130\uD2F0, \uBB38\uC81C \uC5C6\uC74C.` : `\u{1F50D} \uC2A4\uD0A4\uB9C8 \uAC80\uC99D \uC644\uB8CC: ${data.entities.length}\uAC1C \uC5D4\uD130\uD2F0, ${issues.length}\uAC1C \uC774\uC288 \uBC1C\uACAC.
21923
+
21924
+ ${issues.join("\n")}`;
21702
21925
  return {
21703
- contents: [{
21704
- uri: uri.href,
21705
- mimeType: "application/json",
21706
- text: JSON.stringify(words, null, 2)
21926
+ content: [{
21927
+ type: "text",
21928
+ text: summary
21707
21929
  }]
21708
21930
  };
21931
+ } catch (err) {
21932
+ return {
21933
+ content: [{
21934
+ type: "text",
21935
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
21936
+ }],
21937
+ isError: true
21938
+ };
21709
21939
  }
21710
- );
21711
- server.resource(
21712
- "project-summary",
21713
- "thinkerd://projects/{projectId}/summary",
21714
- {
21715
- description: "\uD504\uB85C\uC81D\uD2B8 \uC804\uCCB4 \uB370\uC774\uD130 \uBAA8\uB378\uC758 \uC694\uC57D(\uC5D4\uD130\uD2F0 \uC218, \uAD00\uACC4 \uC218, \uC8FC\uC81C\uC601\uC5ED \uAD6C\uC131, RDF \uD2B8\uB9AC\uD50C \uC218)\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
21716
- },
21717
- async (uri) => {
21718
- const projectId = extractParam(uri.href, "projects");
21719
- const summary = await client.getProjectSummary(projectId);
21940
+ });
21941
+ server.tool("harvest_schema_semantics", "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uBB3C\uB9AC \uD14C\uC774\uBE14 \uBAA9\uB85D\uC744 \uAE30\uBC18\uC73C\uB85C \uC5ED\uACF5\uD559 \uCD94\uB860\uC744 \uC2E4\uD589\uD558\uC5EC \uB17C\uB9AC\uBA85, \uC124\uBA85, \uAD00\uACC4 \uB4F1\uC758 \uC2DC\uBA58\uD2F1 \uBA54\uD0C0\uB370\uC774\uD130\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.", {
21942
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21943
+ tableNames: external_exports.array(external_exports.string()).optional().describe("\uCD94\uCD9C\uD560 \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85 \uBAA9\uB85D. \uC0DD\uB7B5 \uC2DC \uC804\uCCB4 \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uB300\uC0C1")
21944
+ }, async ({ diagramId, tableNames }) => {
21945
+ try {
21946
+ const data = await client.getDiagram(diagramId);
21947
+ let entities = data.entities;
21948
+ if (tableNames && tableNames.length > 0) {
21949
+ const tableSet = new Set(tableNames.map((t) => t.toUpperCase()));
21950
+ entities = entities.filter((e) => e.tableName && tableSet.has(e.tableName.toUpperCase()));
21951
+ }
21952
+ const { extractFrequentTokens: extractFrequentTokens2 } = await Promise.resolve().then(() => (init_src(), src_exports));
21953
+ const harvested = extractFrequentTokens2(entities, [], 1);
21954
+ const summary = {
21955
+ extractedSemantics: harvested,
21956
+ relationships: data.relationships.filter((r) => entities.some((e) => e.id === r.sourceEntityId) || entities.some((e) => e.id === r.targetEntityId))
21957
+ };
21720
21958
  return {
21721
- contents: [{
21722
- uri: uri.href,
21723
- mimeType: "application/json",
21959
+ content: [{
21960
+ type: "text",
21724
21961
  text: JSON.stringify(summary, null, 2)
21725
21962
  }]
21726
21963
  };
21964
+ } catch (err) {
21965
+ return {
21966
+ content: [{
21967
+ type: "text",
21968
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
21969
+ }],
21970
+ isError: true
21971
+ };
21727
21972
  }
21728
- );
21729
- server.resource(
21730
- "metadata-quality",
21731
- "thinkerd://projects/{projectId}/quality",
21732
- {
21733
- description: "\uD504\uB85C\uC81D\uD2B8\uC758 \uBA54\uD0C0\uB370\uC774\uD130 \uD48D\uBD80\uB3C4(\uC124\uBA85, \uB3D9\uC758\uC5B4, \uC18D\uC131 \uC785\uB825\uB960)\uB97C \uBD84\uC11D\uD55C \uD488\uC9C8 \uBCF4\uACE0\uC11C\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4."
21734
- },
21735
- async (uri) => {
21736
- const projectId = extractParam(uri.href, "projects");
21737
- const report = await client.checkMetadataQuality(projectId);
21973
+ });
21974
+ server.tool("propose_schema_change", "\uC678\uBD80 AI\uAC00 ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0 \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD\uC744 \uC81C\uC548\uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC774 \uD544\uC694\uD55C Draft\uB85C \uC804\uC1A1\uB429\uB2C8\uB2E4.", {
21975
+ diagramId: external_exports.string().describe("\uB300\uC0C1 \uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21976
+ entityName: external_exports.string().describe("\uBCC0\uACBD \uB300\uC0C1 \uC5D4\uD130\uD2F0\uBA85 (\uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)"),
21977
+ changeType: external_exports.enum(["add_column", "modify_column", "add_entity"]).describe("\uBCC0\uACBD \uC720\uD615"),
21978
+ changeData: external_exports.record(external_exports.unknown()).describe("\uBCC0\uACBD \uB370\uC774\uD130 (JSON). add_column: {name, type, logicalName}, add_entity: {name, tableName, columns}"),
21979
+ reason: external_exports.string().describe("\uBCC0\uACBD \uC0AC\uC720")
21980
+ }, async ({ diagramId, entityName, changeType, changeData, reason }) => {
21981
+ try {
21982
+ const draft = await client.proposeSchemaChange(diagramId, {
21983
+ entityName,
21984
+ changeType,
21985
+ changeData,
21986
+ reason
21987
+ });
21738
21988
  return {
21739
- contents: [{
21740
- uri: uri.href,
21741
- mimeType: "application/json",
21742
- text: JSON.stringify(report, null, 2)
21989
+ content: [{
21990
+ type: "text",
21991
+ text: `\u2705 \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD \uC81C\uC548\uC774 ThinkERD\uC5D0 \uC804\uC1A1\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
21992
+
21993
+ Draft ID: ${draft.draft_id}
21994
+ \uC0C1\uD0DC: ${draft.status}
21995
+ \uB300\uC0C1: ${entityName}
21996
+ \uC720\uD615: ${changeType}
21997
+ \uC0AC\uC720: ${reason}
21998
+
21999
+ \u26A0\uFE0F ThinkERD\uC5D0\uC11C \uC0AC\uC6A9\uC790\uAC00 \uC774 \uC81C\uC548\uC744 \uC2B9\uC778\uD574\uC57C \uC801\uC6A9\uB429\uB2C8\uB2E4.`
21743
22000
  }]
21744
22001
  };
22002
+ } catch (err) {
22003
+ return {
22004
+ content: [{
22005
+ type: "text",
22006
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22007
+ }],
22008
+ isError: true
22009
+ };
21745
22010
  }
21746
- );
21747
- }
21748
- function extractParam(href, segment) {
21749
- const parts = href.split("/");
21750
- const idx = parts.indexOf(segment);
21751
- if (idx === -1 || idx + 1 >= parts.length) {
21752
- throw new Error(`Invalid URI: cannot extract '${segment}' parameter from ${href}`);
21753
- }
21754
- return parts[idx + 1];
21755
- }
21756
-
21757
- // src/tools.ts
21758
- function registerTools(server, client) {
21759
- server.tool(
21760
- "get_entity",
21761
- "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0\uC11C \uD2B9\uC815 \uC5D4\uD130\uD2F0\uC758 \uCEEC\uB7FC, \uD0C0\uC785, PK/FK \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
21762
- {
21763
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21764
- entityId: external_exports.string().describe("\uC5D4\uD130\uD2F0 ID")
21765
- },
21766
- async ({ diagramId, entityId }) => {
21767
- try {
21768
- const entity = await client.getEntity(diagramId, entityId);
21769
- return {
21770
- content: [{
21771
- type: "text",
21772
- text: JSON.stringify(entity, null, 2)
21773
- }]
21774
- };
21775
- } catch (err) {
21776
- return {
21777
- content: [{
21778
- type: "text",
21779
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
21780
- }],
21781
- isError: true
21782
- };
22011
+ });
22012
+ server.tool("get_business_dictionary", "\uC0AC\uB0B4 \uD45C\uC900 \uB2E8\uC5B4/\uC6A9\uC5B4 \uC0AC\uC804\uC5D0\uC11C \uD0A4\uC6CC\uB4DC\uB97C \uAC80\uC0C9\uD558\uC5EC \uB17C\uB9AC\uBA85-\uBB3C\uB9AC\uBA85 \uB9E4\uD551 \uAC00\uC774\uB4DC\uB97C \uC81C\uACF5\uD569\uB2C8\uB2E4. \uAC01 \uB2E8\uC5B4\uC758 \uB3D9\uC758\uC5B4(synonyms)\uB3C4 \uD568\uAED8 \uBC18\uD658\uD558\uBBC0\uB85C, \uC0AC\uC6A9\uC790\uAC00 \uC4F4 \uB9D0\uC774 \uC0AC\uB0B4\uC5D0\uC11C \uC5B4\uB5A4 \uD45C\uC900 \uB2E8\uC5B4\uC778\uC9C0 \uB418\uC9DA\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.", {
22013
+ projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22014
+ keyword: external_exports.string().optional().describe("\uAC80\uC0C9\uC5B4 (\uC0DD\uB7B5 \uC2DC \uC804\uCCB4 \uC870\uD68C). \uB3D9\uC758\uC5B4\uB85C\uB3C4 \uAC80\uC0C9\uB429\uB2C8\uB2E4")
22015
+ }, async ({ projectId, keyword }) => {
22016
+ try {
22017
+ let dictionary = await client.getDictionary(projectId);
22018
+ if (keyword) {
22019
+ const lowerKeyword = keyword.toLowerCase();
22020
+ dictionary = dictionary.filter((w) => w.logicalName.toLowerCase().includes(lowerKeyword) || w.physicalName.toLowerCase().includes(lowerKeyword) || (w.synonyms ?? []).some((s) => s.toLowerCase().includes(lowerKeyword)) || w.classifier && w.classifier.toLowerCase().includes(lowerKeyword));
21783
22021
  }
22022
+ return {
22023
+ content: [{
22024
+ type: "text",
22025
+ text: JSON.stringify(dictionary, null, 2)
22026
+ }]
22027
+ };
22028
+ } catch (err) {
22029
+ return {
22030
+ content: [{
22031
+ type: "text",
22032
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22033
+ }],
22034
+ isError: true
22035
+ };
21784
22036
  }
21785
- );
21786
- server.tool(
21787
- "search_entities",
21788
- "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0\uC11C \uC774\uB984\uC73C\uB85C \uC5D4\uD130\uD2F0\uB97C \uAC80\uC0C9\uD569\uB2C8\uB2E4. \uB17C\uB9AC\uBA85/\uBB3C\uB9AC\uBA85 \uBAA8\uB450 \uAC80\uC0C9 \uAC00\uB2A5\uD569\uB2C8\uB2E4.",
21789
- {
21790
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21791
- query: external_exports.string().describe("\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC (\uC5D4\uD130\uD2F0 \uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)")
21792
- },
21793
- async ({ diagramId, query }) => {
21794
- try {
21795
- const entities = await client.searchEntities(diagramId, query);
21796
- const summary = entities.map((e) => ({
21797
- id: e.id,
21798
- name: e.name,
22037
+ });
22038
+ server.tool("analyze_legacy_sql", "\uBCF5\uC7A1\uD55C \uB808\uAC70\uC2DC SQL \uCFFC\uB9AC\uB97C \uBD84\uC11D\uD558\uC5EC ERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0 \uAE30\uBC18\uD55C \uD14C\uC774\uBE14\uB4E4\uC758 \uBE44\uC988\uB2C8\uC2A4 \uC758\uBBF8 \uBC0F \uC5F0\uAD00 \uAD00\uACC4\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.", {
22039
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22040
+ sqlQuery: external_exports.string().describe("\uBD84\uC11D\uD560 \uB300\uC0C1 SQL \uCFFC\uB9AC")
22041
+ }, async ({ diagramId, sqlQuery }) => {
22042
+ try {
22043
+ const data = await client.getDiagram(diagramId);
22044
+ const queryUpper = sqlQuery.toUpperCase();
22045
+ const matchedEntities = data.entities.filter((e) => e.tableName && queryUpper.includes(e.tableName.toUpperCase()));
22046
+ const matchedEntityIds = new Set(matchedEntities.map((e) => e.id));
22047
+ const matchedRelationships = data.relationships.filter((r) => matchedEntityIds.has(r.sourceEntityId) || matchedEntityIds.has(r.targetEntityId));
22048
+ const { extractFrequentTokens: extractFrequentTokens2 } = await Promise.resolve().then(() => (init_src(), src_exports));
22049
+ const harvested = extractFrequentTokens2(matchedEntities, [], 1);
22050
+ const result = {
22051
+ message: "SQL \uB0B4\uC5D0\uC11C \uC2DD\uBCC4\uB41C \uD14C\uC774\uBE14\uC5D0 \uB300\uD55C \uBE44\uC988\uB2C8\uC2A4 \uBD84\uC11D \uACB0\uACFC\uC785\uB2C8\uB2E4.",
22052
+ matchedTables: matchedEntities.map((e) => ({
21799
22053
  tableName: e.tableName,
21800
- role: e.entityRole,
21801
- columnCount: e.columns.length,
21802
- columns: e.columns.map((c) => `${c.name} (${c.type})${c.isPrimaryKey ? " [PK]" : ""}${c.isForeignKey ? " [FK]" : ""}`)
21803
- }));
21804
- return {
21805
- content: [{
21806
- type: "text",
21807
- text: JSON.stringify(summary, null, 2)
21808
- }]
21809
- };
21810
- } catch (err) {
21811
- return {
21812
- content: [{
21813
- type: "text",
21814
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
21815
- }],
21816
- isError: true
21817
- };
21818
- }
21819
- }
21820
- );
21821
- server.tool(
21822
- "generate_ddl",
21823
- "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 DDL(CREATE TABLE \uAD6C\uBB38)\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4. PostgreSQL, MySQL, Oracle \uB4F1 \uB2E4\uC591\uD55C \uBC29\uC5B8\uC744 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.",
21824
- {
21825
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21826
- dialect: external_exports.enum(["postgresql", "mysql", "oracle", "mssql", "sqlite"]).optional().default("postgresql").describe("SQL \uBC29\uC5B8 (\uAE30\uBCF8\uAC12: postgresql)")
21827
- },
21828
- async ({ diagramId, dialect }) => {
21829
- try {
21830
- const ddl = await client.generateDDL(diagramId, dialect);
21831
- return {
21832
- content: [{
21833
- type: "text",
21834
- text: typeof ddl === "string" ? ddl : JSON.stringify(ddl)
21835
- }]
21836
- };
21837
- } catch (err) {
21838
- return {
21839
- content: [{
21840
- type: "text",
21841
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
21842
- }],
21843
- isError: true
21844
- };
21845
- }
21846
- }
21847
- );
21848
- server.tool(
21849
- "validate_schema",
21850
- "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC2A4\uD0A4\uB9C8\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4. \uB204\uB77D\uB41C PK, \uBBF8\uC5F0\uACB0 FK, \uBE44\uD45C\uC900 \uB124\uC774\uBC0D \uB4F1\uC744 \uC810\uAC80\uD569\uB2C8\uB2E4.",
21851
- {
21852
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID")
21853
- },
21854
- async ({ diagramId }) => {
21855
- try {
21856
- const data = await client.getDiagram(diagramId);
21857
- const issues = [];
21858
- for (const entity of data.entities) {
21859
- const hasPK = entity.columns.some((c) => c.isPrimaryKey);
21860
- if (!hasPK) {
21861
- issues.push(`\u26A0\uFE0F [${entity.name}] PK(Primary Key)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`);
21862
- }
21863
- }
21864
- for (const entity of data.entities) {
21865
- if (entity.columns.length === 0) {
21866
- issues.push(`\u26A0\uFE0F [${entity.name}] \uCEEC\uB7FC\uC774 \uD558\uB098\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4.`);
21867
- }
21868
- }
21869
- const connectedIds = /* @__PURE__ */ new Set();
21870
- for (const rel of data.relationships) {
21871
- connectedIds.add(rel.sourceEntityId);
21872
- connectedIds.add(rel.targetEntityId);
21873
- }
21874
- for (const entity of data.entities) {
21875
- if (!connectedIds.has(entity.id) && data.entities.length > 1) {
21876
- issues.push(`\u2139\uFE0F [${entity.name}] \uB2E4\uB978 \uC5D4\uD130\uD2F0\uC640 \uAD00\uACC4\uAC00 \uC5C6\uB294 \uACE0\uB9BD \uC5D4\uD130\uD2F0\uC785\uB2C8\uB2E4.`);
21877
- }
21878
- }
21879
- const summary = issues.length === 0 ? `\u2705 \uC2A4\uD0A4\uB9C8 \uAC80\uC99D \uC644\uB8CC: ${data.entities.length}\uAC1C \uC5D4\uD130\uD2F0, \uBB38\uC81C \uC5C6\uC74C.` : `\u{1F50D} \uC2A4\uD0A4\uB9C8 \uAC80\uC99D \uC644\uB8CC: ${data.entities.length}\uAC1C \uC5D4\uD130\uD2F0, ${issues.length}\uAC1C \uC774\uC288 \uBC1C\uACAC.
21880
-
21881
- ${issues.join("\n")}`;
21882
- return {
21883
- content: [{
21884
- type: "text",
21885
- text: summary
21886
- }]
21887
- };
21888
- } catch (err) {
21889
- return {
21890
- content: [{
21891
- type: "text",
21892
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
21893
- }],
21894
- isError: true
21895
- };
21896
- }
21897
- }
21898
- );
21899
- server.tool(
21900
- "harvest_schema_semantics",
21901
- "ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uBB3C\uB9AC \uD14C\uC774\uBE14 \uBAA9\uB85D\uC744 \uAE30\uBC18\uC73C\uB85C \uC5ED\uACF5\uD559 \uCD94\uB860\uC744 \uC2E4\uD589\uD558\uC5EC \uB17C\uB9AC\uBA85, \uC124\uBA85, \uAD00\uACC4 \uB4F1\uC758 \uC2DC\uBA58\uD2F1 \uBA54\uD0C0\uB370\uC774\uD130\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.",
21902
- {
21903
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21904
- tableNames: external_exports.array(external_exports.string()).optional().describe("\uCD94\uCD9C\uD560 \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85 \uBAA9\uB85D. \uC0DD\uB7B5 \uC2DC \uC804\uCCB4 \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uB300\uC0C1")
21905
- },
21906
- async ({ diagramId, tableNames }) => {
21907
- try {
21908
- const data = await client.getDiagram(diagramId);
21909
- let entities = data.entities;
21910
- if (tableNames && tableNames.length > 0) {
21911
- const tableSet = new Set(tableNames.map((t) => t.toUpperCase()));
21912
- entities = entities.filter((e) => e.tableName && tableSet.has(e.tableName.toUpperCase()));
21913
- }
21914
- const { extractFrequentTokens: extractFrequentTokens2 } = await Promise.resolve().then(() => (init_src(), src_exports));
21915
- const harvested = extractFrequentTokens2(entities, [], 1);
21916
- const summary = {
21917
- extractedSemantics: harvested,
21918
- relationships: data.relationships.filter(
21919
- (r) => entities.some((e) => e.id === r.sourceEntityId) || entities.some((e) => e.id === r.targetEntityId)
21920
- )
21921
- };
21922
- return {
21923
- content: [{
21924
- type: "text",
21925
- text: JSON.stringify(summary, null, 2)
21926
- }]
21927
- };
21928
- } catch (err) {
21929
- return {
21930
- content: [{
21931
- type: "text",
21932
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
21933
- }],
21934
- isError: true
21935
- };
21936
- }
21937
- }
21938
- );
21939
- server.tool(
21940
- "propose_schema_change",
21941
- "\uC678\uBD80 AI\uAC00 ThinkERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0 \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD\uC744 \uC81C\uC548\uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC774 \uD544\uC694\uD55C Draft\uB85C \uC804\uC1A1\uB429\uB2C8\uB2E4.",
21942
- {
21943
- diagramId: external_exports.string().describe("\uB300\uC0C1 \uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
21944
- entityName: external_exports.string().describe("\uBCC0\uACBD \uB300\uC0C1 \uC5D4\uD130\uD2F0\uBA85 (\uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)"),
21945
- changeType: external_exports.enum(["add_column", "modify_column", "add_entity"]).describe("\uBCC0\uACBD \uC720\uD615"),
21946
- changeData: external_exports.record(external_exports.unknown()).describe("\uBCC0\uACBD \uB370\uC774\uD130 (JSON). add_column: {name, type, logicalName}, add_entity: {name, tableName, columns}"),
21947
- reason: external_exports.string().describe("\uBCC0\uACBD \uC0AC\uC720")
21948
- },
21949
- async ({ diagramId, entityName, changeType, changeData, reason }) => {
21950
- try {
21951
- const draft = await client.proposeSchemaChange(diagramId, {
21952
- entityName,
21953
- changeType,
21954
- changeData,
21955
- reason
21956
- });
21957
- return {
21958
- content: [{
21959
- type: "text",
21960
- text: `\u2705 \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD \uC81C\uC548\uC774 ThinkERD\uC5D0 \uC804\uC1A1\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
21961
-
21962
- Draft ID: ${draft.draft_id}
21963
- \uC0C1\uD0DC: ${draft.status}
21964
- \uB300\uC0C1: ${entityName}
21965
- \uC720\uD615: ${changeType}
21966
- \uC0AC\uC720: ${reason}
21967
-
21968
- \u26A0\uFE0F ThinkERD\uC5D0\uC11C \uC0AC\uC6A9\uC790\uAC00 \uC774 \uC81C\uC548\uC744 \uC2B9\uC778\uD574\uC57C \uC801\uC6A9\uB429\uB2C8\uB2E4.`
21969
- }]
21970
- };
21971
- } catch (err) {
21972
- return {
21973
- content: [{
21974
- type: "text",
21975
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
21976
- }],
21977
- isError: true
21978
- };
21979
- }
21980
- }
21981
- );
21982
- server.tool(
21983
- "get_business_dictionary",
21984
- "\uC0AC\uB0B4 \uD45C\uC900 \uB2E8\uC5B4/\uC6A9\uC5B4 \uC0AC\uC804\uC5D0\uC11C \uD0A4\uC6CC\uB4DC\uB97C \uAC80\uC0C9\uD558\uC5EC \uB17C\uB9AC\uBA85-\uBB3C\uB9AC\uBA85 \uB9E4\uD551 \uAC00\uC774\uB4DC\uB97C \uC81C\uACF5\uD569\uB2C8\uB2E4. \uAC01 \uB2E8\uC5B4\uC758 \uB3D9\uC758\uC5B4(synonyms)\uB3C4 \uD568\uAED8 \uBC18\uD658\uD558\uBBC0\uB85C, \uC0AC\uC6A9\uC790\uAC00 \uC4F4 \uB9D0\uC774 \uC0AC\uB0B4\uC5D0\uC11C \uC5B4\uB5A4 \uD45C\uC900 \uB2E8\uC5B4\uC778\uC9C0 \uB418\uC9DA\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
21985
- {
21986
- projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
21987
- keyword: external_exports.string().optional().describe("\uAC80\uC0C9\uC5B4 (\uC0DD\uB7B5 \uC2DC \uC804\uCCB4 \uC870\uD68C). \uB3D9\uC758\uC5B4\uB85C\uB3C4 \uAC80\uC0C9\uB429\uB2C8\uB2E4")
21988
- },
21989
- async ({ projectId, keyword }) => {
21990
- try {
21991
- let dictionary = await client.getDictionary(projectId);
21992
- if (keyword) {
21993
- const lowerKeyword = keyword.toLowerCase();
21994
- dictionary = dictionary.filter(
21995
- (w) => w.logicalName.toLowerCase().includes(lowerKeyword) || w.physicalName.toLowerCase().includes(lowerKeyword) || (w.synonyms ?? []).some((s) => s.toLowerCase().includes(lowerKeyword)) || w.classifier && w.classifier.toLowerCase().includes(lowerKeyword)
21996
- );
21997
- }
21998
- return {
21999
- content: [{
22000
- type: "text",
22001
- text: JSON.stringify(dictionary, null, 2)
22002
- }]
22003
- };
22004
- } catch (err) {
22005
- return {
22006
- content: [{
22007
- type: "text",
22008
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22009
- }],
22010
- isError: true
22011
- };
22012
- }
22054
+ logicalName: e.logicalName,
22055
+ description: e.columns.map((c) => `${c.name}(${c.type}) ${c.logicalName || ""}`).join(", ")
22056
+ })),
22057
+ semanticHarvest: harvested,
22058
+ relevantRelationships: matchedRelationships
22059
+ };
22060
+ return {
22061
+ content: [{
22062
+ type: "text",
22063
+ text: JSON.stringify(result, null, 2)
22064
+ }]
22065
+ };
22066
+ } catch (err) {
22067
+ return {
22068
+ content: [{
22069
+ type: "text",
22070
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22071
+ }],
22072
+ isError: true
22073
+ };
22013
22074
  }
22014
- );
22015
- server.tool(
22016
- "analyze_legacy_sql",
22017
- "\uBCF5\uC7A1\uD55C \uB808\uAC70\uC2DC SQL \uCFFC\uB9AC\uB97C \uBD84\uC11D\uD558\uC5EC ERD \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC5D0 \uAE30\uBC18\uD55C \uD14C\uC774\uBE14\uB4E4\uC758 \uBE44\uC988\uB2C8\uC2A4 \uC758\uBBF8 \uBC0F \uC5F0\uAD00 \uAD00\uACC4\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.",
22018
- {
22019
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22020
- sqlQuery: external_exports.string().describe("\uBD84\uC11D\uD560 \uB300\uC0C1 SQL \uCFFC\uB9AC")
22021
- },
22022
- async ({ diagramId, sqlQuery }) => {
22023
- try {
22024
- const data = await client.getDiagram(diagramId);
22025
- const queryUpper = sqlQuery.toUpperCase();
22026
- const matchedEntities = data.entities.filter(
22027
- (e) => e.tableName && queryUpper.includes(e.tableName.toUpperCase())
22028
- );
22029
- const matchedEntityIds = new Set(matchedEntities.map((e) => e.id));
22030
- const matchedRelationships = data.relationships.filter(
22031
- (r) => matchedEntityIds.has(r.sourceEntityId) || matchedEntityIds.has(r.targetEntityId)
22032
- );
22033
- const { extractFrequentTokens: extractFrequentTokens2 } = await Promise.resolve().then(() => (init_src(), src_exports));
22034
- const harvested = extractFrequentTokens2(matchedEntities, [], 1);
22035
- const result = {
22036
- message: "SQL \uB0B4\uC5D0\uC11C \uC2DD\uBCC4\uB41C \uD14C\uC774\uBE14\uC5D0 \uB300\uD55C \uBE44\uC988\uB2C8\uC2A4 \uBD84\uC11D \uACB0\uACFC\uC785\uB2C8\uB2E4.",
22037
- matchedTables: matchedEntities.map((e) => ({
22075
+ });
22076
+ server.tool("semantic_search", "\uBE44\uC988\uB2C8\uC2A4 \uC6A9\uC5B4(\uC790\uC5F0\uC5B4)\uB85C \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC5D4\uD130\uD2F0\xB7\uCEEC\uB7FC\uC744 \uAC80\uC0C9\uD569\uB2C8\uB2E4. \uB3D9\uC758\uC5B4, \uC124\uBA85, \uB17C\uB9AC\uBA85\uC744 \uBAA8\uB450 \uD65C\uC6A9\uD558\uC5EC \uB9E4\uCE6D\uD558\uBA70, \uB9E4\uCE6D \uC720\uD615(\uB17C\uB9AC\uBA85/\uB3D9\uC758\uC5B4/\uC124\uBA85/\uBD80\uBD84\uB9E4\uCE6D/\uCEEC\uB7FC)\uC744 \uD568\uAED8 \uBC18\uD658\uD569\uB2C8\uB2E4.", {
22077
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22078
+ query: external_exports.string().describe("\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC (\uBE44\uC988\uB2C8\uC2A4 \uC6A9\uC5B4, \uB17C\uB9AC\uBA85, \uB3D9\uC758\uC5B4 \uB4F1)"),
22079
+ maxResults: external_exports.number().optional().describe("\uCD5C\uB300 \uACB0\uACFC \uC218 (\uAE30\uBCF8 10)")
22080
+ }, async ({ diagramId, query, maxResults }) => {
22081
+ try {
22082
+ const data = await client.getDiagram(diagramId);
22083
+ const entities = data.entities || [];
22084
+ const limit = maxResults || 10;
22085
+ const q = query.toLowerCase();
22086
+ const results = [];
22087
+ for (const entity of entities) {
22088
+ const e = entity;
22089
+ if (e.name?.toLowerCase().includes(q) || e.logicalName?.toLowerCase().includes(q)) {
22090
+ results.push({
22091
+ entityId: e.id,
22092
+ entityName: e.name,
22038
22093
  tableName: e.tableName,
22039
- logicalName: e.logicalName,
22040
- description: e.columns.map((c) => `${c.name}(${c.type}) ${c.logicalName || ""}`).join(", ")
22041
- })),
22042
- semanticHarvest: harvested,
22043
- relevantRelationships: matchedRelationships
22044
- };
22045
- return {
22046
- content: [{
22047
- type: "text",
22048
- text: JSON.stringify(result, null, 2)
22049
- }]
22050
- };
22051
- } catch (err) {
22052
- return {
22053
- content: [{
22054
- type: "text",
22055
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22056
- }],
22057
- isError: true
22058
- };
22059
- }
22060
- }
22061
- );
22062
- server.tool(
22063
- "semantic_search",
22064
- "\uBE44\uC988\uB2C8\uC2A4 \uC6A9\uC5B4(\uC790\uC5F0\uC5B4)\uB85C \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC5D4\uD130\uD2F0\xB7\uCEEC\uB7FC\uC744 \uAC80\uC0C9\uD569\uB2C8\uB2E4. \uB3D9\uC758\uC5B4, \uC124\uBA85, \uB17C\uB9AC\uBA85\uC744 \uBAA8\uB450 \uD65C\uC6A9\uD558\uC5EC \uB9E4\uCE6D\uD558\uBA70, \uB9E4\uCE6D \uC720\uD615(\uB17C\uB9AC\uBA85/\uB3D9\uC758\uC5B4/\uC124\uBA85/\uBD80\uBD84\uB9E4\uCE6D/\uCEEC\uB7FC)\uC744 \uD568\uAED8 \uBC18\uD658\uD569\uB2C8\uB2E4.",
22065
- {
22066
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22067
- query: external_exports.string().describe("\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC (\uBE44\uC988\uB2C8\uC2A4 \uC6A9\uC5B4, \uB17C\uB9AC\uBA85, \uB3D9\uC758\uC5B4 \uB4F1)"),
22068
- maxResults: external_exports.number().optional().describe("\uCD5C\uB300 \uACB0\uACFC \uC218 (\uAE30\uBCF8 10)")
22069
- },
22070
- async ({ diagramId, query, maxResults }) => {
22071
- try {
22072
- const data = await client.getDiagram(diagramId);
22073
- const entities = data.entities || [];
22074
- const limit = maxResults || 10;
22075
- const q = query.toLowerCase();
22076
- const results = [];
22077
- for (const entity of entities) {
22078
- const e = entity;
22079
- if (e.name?.toLowerCase().includes(q) || e.logicalName?.toLowerCase().includes(q)) {
22094
+ matchType: "logicalName",
22095
+ matchedOn: e.name || e.logicalName,
22096
+ description: e.description || null
22097
+ });
22098
+ continue;
22099
+ }
22100
+ if (Array.isArray(e.synonyms)) {
22101
+ const syn = e.synonyms.find((s) => s.toLowerCase().includes(q));
22102
+ if (syn) {
22080
22103
  results.push({
22081
22104
  entityId: e.id,
22082
22105
  entityName: e.name,
22083
22106
  tableName: e.tableName,
22084
- matchType: "logicalName",
22085
- matchedOn: e.name || e.logicalName,
22107
+ matchType: "synonym",
22108
+ matchedOn: syn,
22086
22109
  description: e.description || null
22087
22110
  });
22088
22111
  continue;
22089
22112
  }
22090
- if (Array.isArray(e.synonyms)) {
22091
- const syn = e.synonyms.find((s) => s.toLowerCase().includes(q));
22092
- if (syn) {
22093
- results.push({
22094
- entityId: e.id,
22095
- entityName: e.name,
22096
- tableName: e.tableName,
22097
- matchType: "synonym",
22098
- matchedOn: syn,
22099
- description: e.description || null
22100
- });
22101
- continue;
22102
- }
22103
- }
22104
- if (e.description?.toLowerCase().includes(q)) {
22113
+ }
22114
+ if (e.description?.toLowerCase().includes(q)) {
22115
+ results.push({
22116
+ entityId: e.id,
22117
+ entityName: e.name,
22118
+ tableName: e.tableName,
22119
+ matchType: "description",
22120
+ matchedOn: e.description
22121
+ });
22122
+ continue;
22123
+ }
22124
+ for (const col of entity.columns) {
22125
+ const c = col;
22126
+ if (c.logicalName?.toLowerCase().includes(q) || c.name?.toLowerCase().includes(q) || c.description?.toLowerCase().includes(q)) {
22105
22127
  results.push({
22106
22128
  entityId: e.id,
22107
22129
  entityName: e.name,
22108
22130
  tableName: e.tableName,
22109
- matchType: "description",
22110
- matchedOn: e.description
22131
+ matchType: "column",
22132
+ matchedColumn: c.name,
22133
+ matchedColumnLogical: c.logicalName
22111
22134
  });
22112
- continue;
22113
- }
22114
- for (const col of entity.columns) {
22115
- const c = col;
22116
- if (c.logicalName?.toLowerCase().includes(q) || c.name?.toLowerCase().includes(q) || c.description?.toLowerCase().includes(q)) {
22117
- results.push({
22118
- entityId: e.id,
22119
- entityName: e.name,
22120
- tableName: e.tableName,
22121
- matchType: "column",
22122
- matchedColumn: c.name,
22123
- matchedColumnLogical: c.logicalName
22124
- });
22125
- break;
22126
- }
22135
+ break;
22127
22136
  }
22128
22137
  }
22129
- return {
22130
- content: [{
22131
- type: "text",
22132
- text: JSON.stringify({
22133
- query,
22134
- totalResults: results.length,
22135
- results: results.slice(0, limit)
22136
- }, null, 2)
22137
- }]
22138
- };
22139
- } catch (err) {
22140
- return {
22141
- content: [{
22142
- type: "text",
22143
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22144
- }],
22145
- isError: true
22146
- };
22147
22138
  }
22139
+ return {
22140
+ content: [{
22141
+ type: "text",
22142
+ text: JSON.stringify({
22143
+ query,
22144
+ totalResults: results.length,
22145
+ results: results.slice(0, limit)
22146
+ }, null, 2)
22147
+ }]
22148
+ };
22149
+ } catch (err) {
22150
+ return {
22151
+ content: [{
22152
+ type: "text",
22153
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22154
+ }],
22155
+ isError: true
22156
+ };
22148
22157
  }
22149
- );
22150
- server.tool(
22151
- "get_business_context",
22152
- "\uD2B9\uC815 \uC5D4\uD130\uD2F0\uC758 \uBE44\uC988\uB2C8\uC2A4 \uC758\uBBF8, \uB3D9\uC758\uC5B4, \uCF54\uB4DC\uAC12, \uAD00\uACC4\uB97C \uD3EC\uD568\uD55C \uC804\uCCB4 \uBE44\uC988\uB2C8\uC2A4 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Text-to-SQL\uC774\uB098 \uBB38\uC11C \uC0DD\uC131\uC758 \uCC38\uACE0\uC790\uB8CC\uB85C \uD65C\uC6A9\uB429\uB2C8\uB2E4.",
22153
- {
22154
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22155
- entityId: external_exports.string().describe("\uC5D4\uD130\uD2F0 ID")
22156
- },
22157
- async ({ diagramId, entityId }) => {
22158
- try {
22159
- const entity = await client.getEntity(diagramId, entityId);
22160
- const data = await client.getDiagram(diagramId);
22161
- const e = entity;
22162
- const relationships = (data.relationships || []).filter(
22163
- (r) => r.sourceEntityId === entityId || r.targetEntityId === entityId
22164
- );
22165
- const businessContext = {
22166
- entity: {
22167
- id: e.id,
22168
- name: e.name,
22169
- tableName: e.tableName,
22170
- logicalName: e.logicalName || e.name,
22171
- description: e.description || null,
22172
- synonyms: e.synonyms || [],
22173
- entityRole: e.entityRole || null
22174
- },
22175
- columns: entity.columns.map((c) => ({
22158
+ });
22159
+ server.tool("get_business_context", "\uD2B9\uC815 \uC5D4\uD130\uD2F0\uC758 \uBE44\uC988\uB2C8\uC2A4 \uC758\uBBF8, \uB3D9\uC758\uC5B4, \uCF54\uB4DC\uAC12, \uAD00\uACC4\uB97C \uD3EC\uD568\uD55C \uC804\uCCB4 \uBE44\uC988\uB2C8\uC2A4 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Text-to-SQL\uC774\uB098 \uBB38\uC11C \uC0DD\uC131\uC758 \uCC38\uACE0\uC790\uB8CC\uB85C \uD65C\uC6A9\uB429\uB2C8\uB2E4.", {
22160
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22161
+ entityId: external_exports.string().describe("\uC5D4\uD130\uD2F0 ID")
22162
+ }, async ({ diagramId, entityId }) => {
22163
+ try {
22164
+ const entity = await client.getEntity(diagramId, entityId);
22165
+ const data = await client.getDiagram(diagramId);
22166
+ const e = entity;
22167
+ const relationships = (data.relationships || []).filter((r) => r.sourceEntityId === entityId || r.targetEntityId === entityId);
22168
+ const businessContext = {
22169
+ entity: {
22170
+ id: e.id,
22171
+ name: e.name,
22172
+ tableName: e.tableName,
22173
+ logicalName: e.logicalName || e.name,
22174
+ description: e.description || null,
22175
+ synonyms: e.synonyms || [],
22176
+ entityRole: e.entityRole || null
22177
+ },
22178
+ columns: entity.columns.map((c) => ({
22179
+ name: c.name,
22180
+ logicalName: c.logicalName || null,
22181
+ type: c.type,
22182
+ isPrimaryKey: c.isPrimaryKey || false,
22183
+ isForeignKey: c.isForeignKey || false,
22184
+ description: c.description || null,
22185
+ domainValues: c.domainValues || null
22186
+ })),
22187
+ relationships: relationships.map((r) => ({
22188
+ relatedEntity: r.sourceEntityId === entityId ? r.targetEntityName : r.sourceEntityName,
22189
+ direction: r.sourceEntityId === entityId ? "outgoing" : "incoming",
22190
+ type: r.type,
22191
+ cardinality: `${r.sourceCardinality}:${r.targetCardinality}`
22192
+ }))
22193
+ };
22194
+ return {
22195
+ content: [{
22196
+ type: "text",
22197
+ text: JSON.stringify(businessContext, null, 2)
22198
+ }]
22199
+ };
22200
+ } catch (err) {
22201
+ return {
22202
+ content: [{
22203
+ type: "text",
22204
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22205
+ }],
22206
+ isError: true
22207
+ };
22208
+ }
22209
+ });
22210
+ server.tool("generate_sql", '**\uD55C \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uBB3C\uB9AC \uC2A4\uD0A4\uB9C8\uB9CC** \uBCF4\uACE0 SQL \uC791\uC131\uC6A9 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. \u26A0\uFE0F \uC774 \uB3C4\uAD6C\uB294 \uD504\uB85C\uC81D\uD2B8 \uC804\uC5ED \uC758\uBBF8 \uACC4\uCE35(\uBE44\uC988\uB2C8\uC2A4 \uBAA8\uB378 \uBC1C\uD589\uBCF8\xB7\uD45C\uC900 \uC0AC\uC804 \uB3D9\uC758\uC5B4\xB7\uC628\uD1A8\uB85C\uC9C0)\uC744 **\uBCF4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.** \uC5C5\uBB34 \uC6A9\uC5B4("\uACE0\uAC1D", "\uBC30\uC1A1\uC911\uC778 \uC8FC\uBB38")\uAC00 \uC11E\uC778 \uC9C8\uBB38\uC774\uB77C\uBA74 **`get_project_context`\uB97C \uBA3C\uC800 \uD638\uCD9C\uD558\uC2ED\uC2DC\uC624.** \uC774 \uB3C4\uAD6C\uB294 \uB300\uC0C1 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC774 \uC774\uBBF8 \uD2B9\uC815\uB418\uC5B4 \uC788\uACE0 \uBB3C\uB9AC \uCEEC\uB7FC \uAD6C\uC870\uB9CC \uD544\uC694\uD560 \uB54C \uC4F0\uC2ED\uC2DC\uC624.', {
22211
+ diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22212
+ question: external_exports.string().describe('\uC790\uC5F0\uC5B4 \uC9C8\uBB38 (\uC608: "\uBD80\uC11C\uBCC4 \uD3C9\uADE0 \uAE09\uC5EC\uAC00 5\uCC9C\uB9CC\uC6D0 \uC774\uC0C1\uC778 \uBD80\uC11C")'),
22213
+ dialect: external_exports.string().optional().describe("SQL \uBC29\uC5B8 (postgresql, mysql, oracle, mssql). \uAE30\uBCF8: postgresql")
22214
+ }, async ({ diagramId, question, dialect }) => {
22215
+ try {
22216
+ const data = await client.getDiagram(diagramId);
22217
+ const entities = data.entities || [];
22218
+ const relationships = data.relationships || [];
22219
+ const schemaContext = entities.map((e) => {
22220
+ const ea = e;
22221
+ return {
22222
+ tableName: e.tableName,
22223
+ logicalName: e.name || ea.logicalName,
22224
+ description: ea.description || null,
22225
+ synonyms: ea.synonyms || [],
22226
+ columns: e.columns.map((c) => ({
22176
22227
  name: c.name,
22177
22228
  logicalName: c.logicalName || null,
22178
22229
  type: c.type,
22179
22230
  isPrimaryKey: c.isPrimaryKey || false,
22180
22231
  isForeignKey: c.isForeignKey || false,
22181
- description: c.description || null,
22182
22232
  domainValues: c.domainValues || null
22183
- })),
22184
- relationships: relationships.map((r) => ({
22185
- relatedEntity: r.sourceEntityId === entityId ? r.targetEntityName : r.sourceEntityName,
22186
- direction: r.sourceEntityId === entityId ? "outgoing" : "incoming",
22187
- type: r.type,
22188
- cardinality: `${r.sourceCardinality}:${r.targetCardinality}`
22189
22233
  }))
22190
22234
  };
22191
- return {
22192
- content: [{
22193
- type: "text",
22194
- text: JSON.stringify(businessContext, null, 2)
22195
- }]
22196
- };
22197
- } catch (err) {
22198
- return {
22199
- content: [{
22200
- type: "text",
22201
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22202
- }],
22203
- isError: true
22204
- };
22205
- }
22206
- }
22207
- );
22208
- server.tool(
22209
- "generate_sql",
22210
- '**\uD55C \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uBB3C\uB9AC \uC2A4\uD0A4\uB9C8\uB9CC** \uBCF4\uACE0 SQL \uC791\uC131\uC6A9 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. \u26A0\uFE0F \uC774 \uB3C4\uAD6C\uB294 \uD504\uB85C\uC81D\uD2B8 \uC804\uC5ED \uC758\uBBF8 \uACC4\uCE35(\uBE44\uC988\uB2C8\uC2A4 \uBAA8\uB378 \uBC1C\uD589\uBCF8\xB7\uD45C\uC900 \uC0AC\uC804 \uB3D9\uC758\uC5B4\xB7\uC628\uD1A8\uB85C\uC9C0)\uC744 **\uBCF4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.** \uC5C5\uBB34 \uC6A9\uC5B4("\uACE0\uAC1D", "\uBC30\uC1A1\uC911\uC778 \uC8FC\uBB38")\uAC00 \uC11E\uC778 \uC9C8\uBB38\uC774\uB77C\uBA74 **`get_project_context`\uB97C \uBA3C\uC800 \uD638\uCD9C\uD558\uC2ED\uC2DC\uC624.** \uC774 \uB3C4\uAD6C\uB294 \uB300\uC0C1 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC774 \uC774\uBBF8 \uD2B9\uC815\uB418\uC5B4 \uC788\uACE0 \uBB3C\uB9AC \uCEEC\uB7FC \uAD6C\uC870\uB9CC \uD544\uC694\uD560 \uB54C \uC4F0\uC2ED\uC2DC\uC624.',
22211
- {
22212
- diagramId: external_exports.string().describe("\uB2E4\uC774\uC5B4\uADF8\uB7A8 ID"),
22213
- question: external_exports.string().describe('\uC790\uC5F0\uC5B4 \uC9C8\uBB38 (\uC608: "\uBD80\uC11C\uBCC4 \uD3C9\uADE0 \uAE09\uC5EC\uAC00 5\uCC9C\uB9CC\uC6D0 \uC774\uC0C1\uC778 \uBD80\uC11C")'),
22214
- dialect: external_exports.string().optional().describe("SQL \uBC29\uC5B8 (postgresql, mysql, oracle, mssql). \uAE30\uBCF8: postgresql")
22215
- },
22216
- async ({ diagramId, question, dialect }) => {
22217
- try {
22218
- const data = await client.getDiagram(diagramId);
22219
- const entities = data.entities || [];
22220
- const relationships = data.relationships || [];
22221
- const schemaContext = entities.map((e) => {
22222
- const ea = e;
22223
- return {
22224
- tableName: e.tableName,
22225
- logicalName: e.name || ea.logicalName,
22226
- description: ea.description || null,
22227
- synonyms: ea.synonyms || [],
22228
- columns: e.columns.map((c) => ({
22229
- name: c.name,
22230
- logicalName: c.logicalName || null,
22231
- type: c.type,
22232
- isPrimaryKey: c.isPrimaryKey || false,
22233
- isForeignKey: c.isForeignKey || false,
22234
- domainValues: c.domainValues || null
22235
- }))
22236
- };
22237
- });
22238
- const relationshipContext = relationships.map((r) => ({
22239
- from: `${r.sourceEntityName} (${r.sourceEntityId})`,
22240
- to: `${r.targetEntityName} (${r.targetEntityId})`,
22241
- type: r.type
22242
- }));
22243
- const result = {
22244
- question,
22245
- dialect: dialect || "postgresql",
22246
- schemaContext,
22247
- relationshipContext,
22248
- hint: `\uC704 \uC2A4\uD0A4\uB9C8 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uAE30\uBC18\uC73C\uB85C "${question}"\uC5D0 \uB300\uD55C ${dialect || "postgresql"} SQL\uC744 \uC0DD\uC131\uD558\uC138\uC694. tableName\uC744 \uD14C\uC774\uBE14 \uCC38\uC870\uC5D0, columns[].name\uC744 \uCEEC\uB7FC \uCC38\uC870\uC5D0 \uC0AC\uC6A9\uD558\uC138\uC694. \uB3D9\uC758\uC5B4(synonyms)\uC640 \uCF54\uB4DC\uAC12(domainValues)\uC744 \uD65C\uC6A9\uD558\uC5EC \uC815\uD655\uD55C \uB9E4\uD551\uC744 \uC218\uD589\uD558\uC138\uC694.`
22249
- };
22250
- return {
22251
- content: [{
22252
- type: "text",
22253
- text: JSON.stringify(result, null, 2)
22254
- }]
22255
- };
22256
- } catch (err) {
22257
- return {
22258
- content: [{
22259
- type: "text",
22260
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22261
- }],
22262
- isError: true
22263
- };
22264
- }
22235
+ });
22236
+ const relationshipContext = relationships.map((r) => ({
22237
+ from: `${r.sourceEntityName} (${r.sourceEntityId})`,
22238
+ to: `${r.targetEntityName} (${r.targetEntityId})`,
22239
+ type: r.type
22240
+ }));
22241
+ const result = {
22242
+ question,
22243
+ dialect: dialect || "postgresql",
22244
+ schemaContext,
22245
+ relationshipContext,
22246
+ hint: `\uC704 \uC2A4\uD0A4\uB9C8 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uAE30\uBC18\uC73C\uB85C "${question}"\uC5D0 \uB300\uD55C ${dialect || "postgresql"} SQL\uC744 \uC0DD\uC131\uD558\uC138\uC694. tableName\uC744 \uD14C\uC774\uBE14 \uCC38\uC870\uC5D0, columns[].name\uC744 \uCEEC\uB7FC \uCC38\uC870\uC5D0 \uC0AC\uC6A9\uD558\uC138\uC694. \uB3D9\uC758\uC5B4(synonyms)\uC640 \uCF54\uB4DC\uAC12(domainValues)\uC744 \uD65C\uC6A9\uD558\uC5EC \uC815\uD655\uD55C \uB9E4\uD551\uC744 \uC218\uD589\uD558\uC138\uC694.`
22247
+ };
22248
+ return {
22249
+ content: [{
22250
+ type: "text",
22251
+ text: JSON.stringify(result, null, 2)
22252
+ }]
22253
+ };
22254
+ } catch (err) {
22255
+ return {
22256
+ content: [{
22257
+ type: "text",
22258
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22259
+ }],
22260
+ isError: true
22261
+ };
22265
22262
  }
22266
- );
22267
- server.tool(
22268
- "get_project_context",
22269
- '**\uC5C5\uBB34 \uC6A9\uC5B4\uAC00 \uC11E\uC778 \uC9C8\uBB38\uC5D0\uB294 \uC774 \uB3C4\uAD6C\uB97C \uBA3C\uC800 \uC4F0\uC2ED\uC2DC\uC624.** \uC790\uC5F0\uC5B4 \uC9C8\uBB38\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8 \uC804\uCCB4\uC5D0\uC11C \uAD00\uB828 \uC5D4\uD130\uD2F0\uB97C \uC2DC\uBA58\uD2F1 \uAC80\uC0C9(Graph RAG)\uD569\uB2C8\uB2E4. \uC774 \uC870\uC9C1\uC774 \uC4F0\uB294 **\uB3D9\uC758\uC5B4\xB7\uCF54\uB4DC\uAC12\uC758 \uB73B\xB7\uBE44\uC988\uB2C8\uC2A4 \uBAA8\uB378 \uBC1C\uD589\uBCF8**\uAE4C\uC9C0 \uBCF4\uBBC0\uB85C, "\uACE0\uAC1D"\uCC98\uB7FC \uBB3C\uB9AC \uC2A4\uD0A4\uB9C8\uC5D0 \uC5C6\uB294 \uB9D0\uB3C4 \uC2E4\uC81C \uD14C\uC774\uBE14\uB85C \uC5F0\uACB0\uD569\uB2C8\uB2E4. \uC751\uB2F5\uC758 `usedFallback: true`\uB294 **\uC9C8\uBB38\uC758 \uAC1C\uB150\uC744 \uCC3E\uC9C0 \uBABB\uD588\uB2E4**\uB294 \uB73B\uC785\uB2C8\uB2E4 \u2014 \uADF8 \uCEE8\uD14D\uC2A4\uD2B8\uB85C SQL\uC744 \uB9CC\uB4E4\uC9C0 \uB9D0\uACE0 \uBAA8\uB978\uB2E4\uACE0 \uB2F5\uD558\uAC70\uB098 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC6A9\uC5B4\uB97C \uB418\uBB3C\uC73C\uC2ED\uC2DC\uC624.',
22270
- {
22271
- projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22272
- question: external_exports.string().describe('\uC790\uC5F0\uC5B4 \uC9C8\uBB38 (\uC608: "\uBD80\uC11C\uBCC4 \uAE09\uC5EC \uCD1D\uC561", "VIP \uACE0\uAC1D\uC758 \uCD5C\uADFC \uC8FC\uBB38")')
22273
- },
22274
- async ({ projectId, question }) => {
22275
- try {
22276
- const result = await client.getProjectContext(projectId, question);
22277
- return {
22278
- content: [{
22279
- type: "text",
22280
- text: JSON.stringify(result, null, 2)
22281
- }]
22282
- };
22283
- } catch (err) {
22284
- return {
22285
- content: [{
22286
- type: "text",
22287
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22288
- }],
22289
- isError: true
22290
- };
22291
- }
22263
+ });
22264
+ server.tool("get_project_context", '**\uC5C5\uBB34 \uC6A9\uC5B4\uAC00 \uC11E\uC778 \uC9C8\uBB38\uC5D0\uB294 \uC774 \uB3C4\uAD6C\uB97C \uBA3C\uC800 \uC4F0\uC2ED\uC2DC\uC624.** \uC790\uC5F0\uC5B4 \uC9C8\uBB38\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8 \uC804\uCCB4\uC5D0\uC11C \uAD00\uB828 \uC5D4\uD130\uD2F0\uB97C \uC2DC\uBA58\uD2F1 \uAC80\uC0C9(Graph RAG)\uD569\uB2C8\uB2E4. \uC774 \uC870\uC9C1\uC774 \uC4F0\uB294 **\uB3D9\uC758\uC5B4\xB7\uCF54\uB4DC\uAC12\uC758 \uB73B\xB7\uBE44\uC988\uB2C8\uC2A4 \uBAA8\uB378 \uBC1C\uD589\uBCF8**\uAE4C\uC9C0 \uBCF4\uBBC0\uB85C, "\uACE0\uAC1D"\uCC98\uB7FC \uBB3C\uB9AC \uC2A4\uD0A4\uB9C8\uC5D0 \uC5C6\uB294 \uB9D0\uB3C4 \uC2E4\uC81C \uD14C\uC774\uBE14\uB85C \uC5F0\uACB0\uD569\uB2C8\uB2E4. \uC751\uB2F5\uC758 `usedFallback: true`\uB294 **\uC9C8\uBB38\uC758 \uAC1C\uB150\uC744 \uCC3E\uC9C0 \uBABB\uD588\uB2E4**\uB294 \uB73B\uC785\uB2C8\uB2E4 \u2014 \uADF8 \uCEE8\uD14D\uC2A4\uD2B8\uB85C SQL\uC744 \uB9CC\uB4E4\uC9C0 \uB9D0\uACE0 \uBAA8\uB978\uB2E4\uACE0 \uB2F5\uD558\uAC70\uB098 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC6A9\uC5B4\uB97C \uB418\uBB3C\uC73C\uC2ED\uC2DC\uC624.', {
22265
+ projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22266
+ question: external_exports.string().describe('\uC790\uC5F0\uC5B4 \uC9C8\uBB38 (\uC608: "\uBD80\uC11C\uBCC4 \uAE09\uC5EC \uCD1D\uC561", "VIP \uACE0\uAC1D\uC758 \uCD5C\uADFC \uC8FC\uBB38")')
22267
+ }, async ({ projectId, question }) => {
22268
+ try {
22269
+ const result = await client.getProjectContext(projectId, question);
22270
+ return {
22271
+ content: [{
22272
+ type: "text",
22273
+ text: JSON.stringify(result, null, 2)
22274
+ }]
22275
+ };
22276
+ } catch (err) {
22277
+ return {
22278
+ content: [{
22279
+ type: "text",
22280
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22281
+ }],
22282
+ isError: true
22283
+ };
22292
22284
  }
22293
- );
22294
- server.tool(
22295
- "find_join_path",
22296
- "\uB450 \uC5D4\uD130\uD2F0(\uD14C\uC774\uBE14) \uAC04 \uCD5C\uC801 JOIN \uACBD\uB85C\uB97C \uD0D0\uC0C9\uD569\uB2C8\uB2E4. BFS \uAE30\uBC18\uC73C\uB85C \uCD5C\uB2E8 \uACBD\uB85C\uB97C \uCC3E\uC73C\uBA70, \uC911\uAC04 \uD14C\uC774\uBE14\uACFC \uAD00\uACC4\uBA85\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. \uAC01 \uAD6C\uAC04\uC5D0 \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85\xB7FK \uCEEC\uB7FC\uC30D(on)\xB7\uC9C4\uD589 \uBC29\uD5A5 \uCE74\uB514\uB110\uB9AC\uD2F0\uAC00 \uC2E4\uB824 \uC788\uC73C\uBBC0\uB85C, **\uC870\uC778 \uC870\uAC74\uC744 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 on \uAC12\uC744 \uADF8\uB300\uB85C ON \uC808\uC5D0 \uC4F0\uC2ED\uC2DC\uC624.** joinable=false\uBA74 FK \uB9E4\uD551\uC774\uB098 \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85\uC774 \uC5C6\uC5B4 SQL\uC744 \uC870\uB9BD\uD560 \uC218 \uC5C6\uB2E4\uB294 \uB73B\uC774\uBA70, \uC0AC\uC720\uB294 warnings\uC5D0 \uC788\uC2B5\uB2C8\uB2E4.",
22297
- {
22298
- projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22299
- from: external_exports.string().describe("\uCD9C\uBC1C \uC5D4\uD130\uD2F0 \uC774\uB984 (\uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)"),
22300
- to: external_exports.string().describe("\uB3C4\uCC29 \uC5D4\uD130\uD2F0 \uC774\uB984 (\uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)")
22301
- },
22302
- async ({ projectId, from, to }) => {
22303
- try {
22304
- const result = await client.findJoinPath(projectId, from, to);
22305
- return {
22306
- content: [{
22307
- type: "text",
22308
- text: JSON.stringify(result, null, 2)
22309
- }]
22310
- };
22311
- } catch (err) {
22312
- return {
22313
- content: [{
22314
- type: "text",
22315
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22316
- }],
22317
- isError: true
22318
- };
22319
- }
22285
+ });
22286
+ server.tool("find_join_path", "\uB450 \uC5D4\uD130\uD2F0(\uD14C\uC774\uBE14) \uAC04 \uCD5C\uC801 JOIN \uACBD\uB85C\uB97C \uD0D0\uC0C9\uD569\uB2C8\uB2E4. BFS \uAE30\uBC18\uC73C\uB85C \uCD5C\uB2E8 \uACBD\uB85C\uB97C \uCC3E\uC73C\uBA70, \uC911\uAC04 \uD14C\uC774\uBE14\uACFC \uAD00\uACC4\uBA85\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. \uAC01 \uAD6C\uAC04\uC5D0 \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85\xB7FK \uCEEC\uB7FC\uC30D(on)\xB7\uC9C4\uD589 \uBC29\uD5A5 \uCE74\uB514\uB110\uB9AC\uD2F0\uAC00 \uC2E4\uB824 \uC788\uC73C\uBBC0\uB85C, **\uC870\uC778 \uC870\uAC74\uC744 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 on \uAC12\uC744 \uADF8\uB300\uB85C ON \uC808\uC5D0 \uC4F0\uC2ED\uC2DC\uC624.** joinable=false\uBA74 FK \uB9E4\uD551\uC774\uB098 \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85\uC774 \uC5C6\uC5B4 SQL\uC744 \uC870\uB9BD\uD560 \uC218 \uC5C6\uB2E4\uB294 \uB73B\uC774\uBA70, \uC0AC\uC720\uB294 warnings\uC5D0 \uC788\uC2B5\uB2C8\uB2E4.", {
22287
+ projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22288
+ from: external_exports.string().describe("\uCD9C\uBC1C \uC5D4\uD130\uD2F0 \uC774\uB984 (\uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)"),
22289
+ to: external_exports.string().describe("\uB3C4\uCC29 \uC5D4\uD130\uD2F0 \uC774\uB984 (\uB17C\uB9AC\uBA85 \uB610\uB294 \uBB3C\uB9AC\uBA85)")
22290
+ }, async ({ projectId, from, to }) => {
22291
+ try {
22292
+ const result = await client.findJoinPath(projectId, from, to);
22293
+ return {
22294
+ content: [{
22295
+ type: "text",
22296
+ text: JSON.stringify(result, null, 2)
22297
+ }]
22298
+ };
22299
+ } catch (err) {
22300
+ return {
22301
+ content: [{
22302
+ type: "text",
22303
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22304
+ }],
22305
+ isError: true
22306
+ };
22320
22307
  }
22321
- );
22322
- server.tool(
22323
- "get_project_summary",
22324
- "\uD504\uB85C\uC81D\uD2B8 \uC804\uCCB4\uC758 \uB370\uC774\uD130 \uBAA8\uB378 \uC694\uC57D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uC5D4\uD130\uD2F0 \uC218, \uAD00\uACC4 \uC218, \uC8FC\uC81C\uC601\uC5ED(Subject Area) \uAD6C\uC131, RDF \uD2B8\uB9AC\uD50C \uC218 \uB4F1\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uC628\uBCF4\uB529\uC774\uB098 \uC804\uCCB4 \uAD6C\uC870 \uD30C\uC545\uC5D0 \uC720\uC6A9\uD569\uB2C8\uB2E4.",
22325
- {
22326
- projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID")
22327
- },
22328
- async ({ projectId }) => {
22329
- try {
22330
- const result = await client.getProjectSummary(projectId);
22331
- return {
22332
- content: [{
22333
- type: "text",
22334
- text: JSON.stringify(result, null, 2)
22335
- }]
22336
- };
22337
- } catch (err) {
22338
- return {
22339
- content: [{
22340
- type: "text",
22341
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22342
- }],
22343
- isError: true
22344
- };
22345
- }
22308
+ });
22309
+ server.tool("get_project_summary", "\uD504\uB85C\uC81D\uD2B8 \uC804\uCCB4\uC758 \uB370\uC774\uD130 \uBAA8\uB378 \uC694\uC57D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uC5D4\uD130\uD2F0 \uC218, \uAD00\uACC4 \uC218, \uC8FC\uC81C\uC601\uC5ED(Subject Area) \uAD6C\uC131, RDF \uD2B8\uB9AC\uD50C \uC218 \uB4F1\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uC628\uBCF4\uB529\uC774\uB098 \uC804\uCCB4 \uAD6C\uC870 \uD30C\uC545\uC5D0 \uC720\uC6A9\uD569\uB2C8\uB2E4.", {
22310
+ projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID")
22311
+ }, async ({ projectId }) => {
22312
+ try {
22313
+ const result = await client.getProjectSummary(projectId);
22314
+ return {
22315
+ content: [{
22316
+ type: "text",
22317
+ text: JSON.stringify(result, null, 2)
22318
+ }]
22319
+ };
22320
+ } catch (err) {
22321
+ return {
22322
+ content: [{
22323
+ type: "text",
22324
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22325
+ }],
22326
+ isError: true
22327
+ };
22346
22328
  }
22347
- );
22348
- server.tool(
22349
- "query_ontology",
22350
- "\uD504\uB85C\uC81D\uD2B8\uC758 \uC628\uD1A8\uB85C\uC9C0 \uADF8\uB798\uD504\uC5D0 SPARQL \uCFFC\uB9AC\uB97C \uC9C1\uC811 \uC2E4\uD589\uD569\uB2C8\uB2E4. \uACE0\uAE09 \uC0AC\uC6A9\uC790\uAC00 \uC815\uAD50\uD55C \uC2DC\uBA58\uD2F1 \uC9C8\uC758\uB97C \uC218\uD589\uD560 \uB54C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",
22351
- {
22352
- projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22353
- query: external_exports.string().describe("SPARQL SELECT \uCFFC\uB9AC\uBB38")
22354
- },
22355
- async ({ projectId, query }) => {
22356
- try {
22357
- const result = await client.queryOntology(projectId, query);
22358
- return {
22359
- content: [{
22360
- type: "text",
22361
- text: JSON.stringify(result, null, 2)
22362
- }]
22363
- };
22364
- } catch (err) {
22365
- return {
22366
- content: [{
22367
- type: "text",
22368
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22369
- }],
22370
- isError: true
22371
- };
22372
- }
22329
+ });
22330
+ server.tool("query_ontology", "\uD504\uB85C\uC81D\uD2B8\uC758 \uC628\uD1A8\uB85C\uC9C0 \uADF8\uB798\uD504\uC5D0 SPARQL \uCFFC\uB9AC\uB97C \uC9C1\uC811 \uC2E4\uD589\uD569\uB2C8\uB2E4. \uACE0\uAE09 \uC0AC\uC6A9\uC790\uAC00 \uC815\uAD50\uD55C \uC2DC\uBA58\uD2F1 \uC9C8\uC758\uB97C \uC218\uD589\uD560 \uB54C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.", {
22331
+ projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
22332
+ query: external_exports.string().describe("SPARQL SELECT \uCFFC\uB9AC\uBB38")
22333
+ }, async ({ projectId, query }) => {
22334
+ try {
22335
+ const result = await client.queryOntology(projectId, query);
22336
+ return {
22337
+ content: [{
22338
+ type: "text",
22339
+ text: JSON.stringify(result, null, 2)
22340
+ }]
22341
+ };
22342
+ } catch (err) {
22343
+ return {
22344
+ content: [{
22345
+ type: "text",
22346
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22347
+ }],
22348
+ isError: true
22349
+ };
22373
22350
  }
22374
- );
22375
- server.tool(
22376
- "check_metadata_quality",
22377
- "\uD504\uB85C\uC81D\uD2B8\uC758 \uBA54\uD0C0\uB370\uC774\uD130 \uD48D\uBD80\uB3C4\uB97C \uBD84\uC11D\uD569\uB2C8\uB2E4. \uC124\uBA85, \uB3D9\uC758\uC5B4, \uC18D\uC131 \uC785\uB825\uB960\uC744 \uAE30\uBC18\uC73C\uB85C \uC804\uCCB4 \uD488\uC9C8 \uC810\uC218\uB97C \uC0B0\uCD9C\uD558\uACE0, \uAC1C\uC120\uC774 \uD544\uC694\uD55C \uC5D4\uD130\uD2F0 \uBAA9\uB85D\uC744 \uC81C\uC548\uD569\uB2C8\uB2E4.",
22378
- {
22379
- projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID")
22380
- },
22381
- async ({ projectId }) => {
22382
- try {
22383
- const result = await client.checkMetadataQuality(projectId);
22384
- const summary = [
22385
- `\u{1F4CA} \uBA54\uD0C0\uB370\uC774\uD130 \uD488\uC9C8 \uBCF4\uACE0\uC11C`,
22386
- `\uD3C9\uADE0 \uD48D\uBD80\uB3C4: ${result.avgRichness}%`,
22387
- `\uCD1D \uC5D4\uD130\uD2F0: ${result.entityCount}\uAC1C`,
22388
- ``,
22389
- `\u{1F7E2} \uC644\uC804 (\u226570%): ${result.breakdown?.complete || 0}\uAC1C`,
22390
- `\u{1F7E1} \uBD80\uBD84 (40-69%): ${result.breakdown?.partial || 0}\uAC1C`,
22391
- `\u{1F534} \uBBF8\uC785\uB825 (<40%): ${result.breakdown?.missing || 0}\uAC1C`
22392
- ];
22393
- if (result.suggestions && result.suggestions.length > 0) {
22394
- summary.push("", "\u{1F4A1} \uAC1C\uC120 \uC81C\uC548 (\uD48D\uBD80\uB3C4 \uB0AE\uC740 \uC21C):");
22395
- for (const s of result.suggestions.slice(0, 10)) {
22396
- summary.push(` \u2022 ${s.entity} (${s.richness}%) \u2014 \uB204\uB77D: ${s.missing.join(", ")}`);
22397
- }
22351
+ });
22352
+ server.tool("check_metadata_quality", "\uD504\uB85C\uC81D\uD2B8\uC758 \uBA54\uD0C0\uB370\uC774\uD130 \uD48D\uBD80\uB3C4\uB97C \uBD84\uC11D\uD569\uB2C8\uB2E4. \uC124\uBA85, \uB3D9\uC758\uC5B4, \uC18D\uC131 \uC785\uB825\uB960\uC744 \uAE30\uBC18\uC73C\uB85C \uC804\uCCB4 \uD488\uC9C8 \uC810\uC218\uB97C \uC0B0\uCD9C\uD558\uACE0, \uAC1C\uC120\uC774 \uD544\uC694\uD55C \uC5D4\uD130\uD2F0 \uBAA9\uB85D\uC744 \uC81C\uC548\uD569\uB2C8\uB2E4.", {
22353
+ projectId: external_exports.string().describe("\uD504\uB85C\uC81D\uD2B8 ID")
22354
+ }, async ({ projectId }) => {
22355
+ try {
22356
+ const result = await client.checkMetadataQuality(projectId);
22357
+ const summary = [
22358
+ `\u{1F4CA} \uBA54\uD0C0\uB370\uC774\uD130 \uD488\uC9C8 \uBCF4\uACE0\uC11C`,
22359
+ `\uD3C9\uADE0 \uD48D\uBD80\uB3C4: ${result.avgRichness}%`,
22360
+ `\uCD1D \uC5D4\uD130\uD2F0: ${result.entityCount}\uAC1C`,
22361
+ ``,
22362
+ `\u{1F7E2} \uC644\uC804 (\u226570%): ${result.breakdown?.complete || 0}\uAC1C`,
22363
+ `\u{1F7E1} \uBD80\uBD84 (40-69%): ${result.breakdown?.partial || 0}\uAC1C`,
22364
+ `\u{1F534} \uBBF8\uC785\uB825 (<40%): ${result.breakdown?.missing || 0}\uAC1C`
22365
+ ];
22366
+ if (result.suggestions && result.suggestions.length > 0) {
22367
+ summary.push("", "\u{1F4A1} \uAC1C\uC120 \uC81C\uC548 (\uD48D\uBD80\uB3C4 \uB0AE\uC740 \uC21C):");
22368
+ for (const s of result.suggestions.slice(0, 10)) {
22369
+ summary.push(` \u2022 ${s.entity} (${s.richness}%) \u2014 \uB204\uB77D: ${s.missing.join(", ")}`);
22398
22370
  }
22399
- return {
22400
- content: [{
22401
- type: "text",
22402
- text: summary.join("\n")
22403
- }]
22404
- };
22405
- } catch (err) {
22406
- return {
22407
- content: [{
22408
- type: "text",
22409
- text: `Error: ${err instanceof Error ? err.message : String(err)}`
22410
- }],
22411
- isError: true
22412
- };
22413
22371
  }
22372
+ return {
22373
+ content: [{
22374
+ type: "text",
22375
+ text: summary.join("\n")
22376
+ }]
22377
+ };
22378
+ } catch (err) {
22379
+ return {
22380
+ content: [{
22381
+ type: "text",
22382
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
22383
+ }],
22384
+ isError: true
22385
+ };
22414
22386
  }
22415
- );
22387
+ });
22416
22388
  }
22417
22389
 
22418
- // src/client.ts
22419
- var ThinkERDClient = class {
22420
- baseUrl;
22421
- headers;
22422
- constructor(apiUrl, token) {
22423
- this.baseUrl = apiUrl.replace(/\/+$/, "");
22424
- this.headers = {
22425
- "Authorization": `Bearer ${token}`,
22426
- "Content-Type": "application/json"
22427
- };
22428
- }
22429
- async request(path) {
22430
- const url = `${this.baseUrl}${path}`;
22431
- const res = await fetch(url, { headers: this.headers });
22432
- if (!res.ok) {
22433
- const body = await res.text().catch(() => "");
22434
- throw new Error(`ThinkERD API error ${res.status}: ${body || res.statusText}`);
22435
- }
22436
- const contentType = res.headers.get("content-type") || "";
22437
- if (contentType.includes("text/plain")) {
22438
- return res.text();
22439
- }
22440
- return res.json();
22441
- }
22442
- // ─── Diagrams ───
22443
- /** 사용자의 모든 다이어그램 목록 조회 */
22444
- async listDiagrams() {
22445
- return this.request("/api/mcp/diagrams");
22446
- }
22447
- /** 다이어그램 상세 (엔터티 + 관계 포함) */
22448
- async getDiagram(diagramId) {
22449
- return this.request(`/api/mcp/diagrams/${diagramId}`);
22450
- }
22451
- // ─── Entities ───
22452
- /** 단일 엔터티 상세 조회 */
22453
- async getEntity(diagramId, entityId) {
22454
- return this.request(`/api/mcp/diagrams/${diagramId}/entities/${entityId}`);
22455
- }
22456
- /** 엔터티 이름으로 검색 */
22457
- async searchEntities(diagramId, query) {
22458
- return this.request(
22459
- `/api/mcp/diagrams/${diagramId}/entities?q=${encodeURIComponent(query)}`
22460
- );
22461
- }
22462
- // ─── DDL ───
22463
- /** 다이어그램의 DDL 생성 */
22464
- async generateDDL(diagramId, dialect) {
22465
- const d = dialect ? `?dialect=${encodeURIComponent(dialect)}` : "";
22466
- return this.request(`/api/mcp/diagrams/${diagramId}/ddl${d}`);
22467
- }
22468
- // ─── Dictionary ───
22469
- /** 표준 사전 단어 목록 */
22470
- async getDictionary(projectId) {
22471
- return this.request(`/api/mcp/projects/${projectId}/dictionary`);
22472
- }
22473
- // ─── Drafts ───
22474
- /** 스키마 변경 제안 (Draft 생성) */
22475
- async proposeSchemaChange(diagramId, data) {
22476
- return this.postRequest(`/api/mcp/diagrams/${diagramId}/drafts`, data);
22477
- }
22478
- // ─── Sync / Push ───
22479
- async postRequest(path, body) {
22480
- const url = `${this.baseUrl}${path}`;
22481
- const res = await fetch(url, {
22482
- method: "POST",
22483
- headers: this.headers,
22484
- body: JSON.stringify(body)
22485
- });
22486
- if (!res.ok) {
22487
- const text = await res.text().catch(() => "");
22488
- throw new Error(`ThinkERD API error ${res.status}: ${text || res.statusText}`);
22489
- }
22490
- return res.json();
22491
- }
22492
- // ─── Context Provider (Project-level Ontology) ───
22493
- /** 자연어 질문 → 관련 엔터티 컨텍스트 (Graph RAG) */
22494
- async getProjectContext(projectId, question) {
22495
- return this.postRequest(`/api/mcp/projects/${projectId}/context`, { question });
22496
- }
22497
- /** 두 엔터티 간 JOIN 경로 탐색 */
22498
- async findJoinPath(projectId, from, to) {
22499
- return this.postRequest(`/api/mcp/projects/${projectId}/join-path`, { from, to });
22500
- }
22501
- /** 프로젝트 전체 스키마 요약 */
22502
- async getProjectSummary(projectId) {
22503
- return this.request(`/api/mcp/projects/${projectId}/summary`);
22504
- }
22505
- /** 메타데이터 풍부도 품질 보고 */
22506
- async checkMetadataQuality(projectId) {
22507
- return this.request(`/api/mcp/projects/${projectId}/quality`);
22508
- }
22509
- /** SPARQL 직접 실행 */
22510
- async queryOntology(projectId, query) {
22511
- return this.postRequest(`/api/ai/ontology/${projectId}/sparql`, { query });
22512
- }
22513
- };
22514
-
22515
- // src/index.ts
22516
- function parseArgs() {
22517
- const args = process.argv.slice(2);
22518
- let apiUrl = process.env.THINKERD_API_URL || "https://api.thinkerd.io";
22519
- let token = process.env.THINKERD_TOKEN || process.env.THINKERD_API_TOKEN || "";
22520
- let devMode = false;
22521
- for (let i = 0; i < args.length; i++) {
22522
- if (args[i] === "--api-url" && args[i + 1]) {
22523
- apiUrl = args[++i];
22524
- } else if (args[i] === "--token" && args[i + 1]) {
22525
- token = args[++i];
22526
- } else if (args[i] === "--dev") {
22527
- devMode = true;
22528
- }
22529
- }
22530
- if (devMode) {
22531
- apiUrl = process.env.THINKERD_API_URL || "http://localhost:8000";
22390
+ // ../cli/dist/commands/mcp.js
22391
+ var SERVER_DESCRIPTION = 'ThinkERD \u2014 \uC6F9 \uAE30\uBC18 ER \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uBAA8\uB378\uB9C1 \uB3C4\uAD6C\uC758 MCP \uC11C\uBC84. \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC2A4\uD0A4\uB9C8 \uC124\uACC4(\uC5D4\uD130\uD2F0/\uCEEC\uB7FC/\uAD00\uACC4)\uB97C \uC870\uD68C\uD558\uACE0, DDL\uC744 \uC0DD\uC131\uD558\uBA70, \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD\uC744 \uC81C\uC548\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD575\uC2EC \uAC1C\uB150: \uB2E4\uC774\uC5B4\uADF8\uB7A8(ERD \uBB38\uC11C), \uC5D4\uD130\uD2F0(\uD14C\uC774\uBE14, \uB17C\uB9AC\uBA85/\uBB3C\uB9AC\uBA85), \uCEEC\uB7FC(\uD0C0\uC785/PK/FK/Nullable), \uAD00\uACC4(1:1/1:N/M:N), \uC8FC\uC81C\uC601\uC5ED(\uC5C5\uBB34 \uB3C4\uBA54\uC778\uBCC4 \uBD84\uB958), \uD504\uB85C\uC81D\uD2B8(\uCD5C\uC0C1\uC704 \uB2E8\uC704), \uD45C\uC900\uC0AC\uC804(\uB17C\uB9AC\u2194\uBB3C\uB9AC \uB9E4\uD551). \uC0AC\uC6A9\uC790\uAC00 "\uC2A4\uD0A4\uB9C8/\uD14C\uC774\uBE14/ERD/\uC5D4\uD130\uD2F0/DDL"\uC744 \uC5B8\uAE09\uD558\uBA74 \uC774 \uC11C\uBC84\uC758 \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uCF54\uB4DC \uC0DD\uC131 \uC694\uCCAD \uC2DC \uBA3C\uC800 \uC2A4\uD0A4\uB9C8\uB97C \uC870\uD68C\uD55C \uD6C4 \uC0DD\uC131\uD558\uC138\uC694.';
22392
+ function findDevToken() {
22393
+ let dir = dirname(fileURLToPath(import.meta.url));
22394
+ for (let depth = 0; depth < 6; depth++) {
22395
+ const candidate = resolve(dir, ".dev-mcp-token");
22396
+ if (existsSync(candidate))
22397
+ return readFileSync(candidate, "utf-8").trim();
22398
+ dir = resolve(dir, "..");
22399
+ }
22400
+ return null;
22401
+ }
22402
+ async function mcpCommand(options) {
22403
+ let apiUrl = options.apiUrl || process.env.THINKERD_API_URL || "https://api.thinkerd.io";
22404
+ let token = options.token || process.env.THINKERD_TOKEN || process.env.THINKERD_API_TOKEN || "";
22405
+ if (options.dev) {
22406
+ apiUrl = options.apiUrl || process.env.THINKERD_API_URL || "http://localhost:8000";
22532
22407
  if (!token) {
22533
- let dir = __dirname;
22534
- for (let depth = 0; depth < 6; depth++) {
22535
- const tokenPath = resolve(dir, ".dev-mcp-token");
22536
- if (existsSync(tokenPath)) {
22537
- token = readFileSync(tokenPath, "utf-8").trim();
22538
- console.error(`[ThinkERD MCP] Dev mode \u2014 loaded PAT from ${tokenPath}`);
22539
- break;
22540
- }
22541
- dir = resolve(dir, "..");
22408
+ const found = findDevToken();
22409
+ if (!found) {
22410
+ console.error("[ThinkERD MCP] Dev mode \u2014 .dev-mcp-token \uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
22411
+ console.error("[ThinkERD MCP] \uBA3C\uC800 \uC11C\uBC84\uB97C \uB744\uC6B0\uC138\uC694: pnpm dev:server");
22412
+ process.exit(1);
22542
22413
  }
22414
+ token = found;
22543
22415
  }
22544
- if (!token) {
22545
- console.error(
22546
- "[ThinkERD MCP] Dev mode \u2014 .dev-mcp-token not found.\nStart the ThinkERD server first (pnpm dev:server) to auto-generate the token."
22547
- );
22548
- process.exit(1);
22549
- }
22550
- console.error("[ThinkERD MCP] Dev mode \u2014 connecting to " + apiUrl);
22416
+ console.error(`[ThinkERD MCP] Dev mode \u2014 connecting to ${apiUrl}`);
22551
22417
  }
22552
22418
  if (!token) {
22553
- console.error(
22554
- "Error: ThinkERD API token is required.\nUsage: thinkerd-mcp --token <YOUR_PAT>\nOr set THINKERD_TOKEN environment variable.\nFor local development: thinkerd-mcp --dev"
22555
- );
22419
+ console.error("Error: ThinkERD API token is required.");
22420
+ console.error("Usage: thinkdata mcp --token <YOUR_PAT>");
22421
+ console.error("Or set THINKERD_TOKEN environment variable.");
22422
+ console.error("For local development: thinkdata mcp --dev");
22556
22423
  process.exit(1);
22557
22424
  }
22558
- return { apiUrl, token, devMode };
22559
- }
22560
- async function main() {
22561
- const { apiUrl, token } = parseArgs();
22562
22425
  const client = new ThinkERDClient(apiUrl, token);
22563
22426
  const server = new McpServer({
22564
22427
  name: "thinkerd",
22565
22428
  version: "0.1.0",
22566
- description: 'ThinkERD \u2014 \uC6F9 \uAE30\uBC18 ER \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uBAA8\uB378\uB9C1 \uB3C4\uAD6C\uC758 MCP \uC11C\uBC84. \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC2A4\uD0A4\uB9C8 \uC124\uACC4(\uC5D4\uD130\uD2F0/\uCEEC\uB7FC/\uAD00\uACC4)\uB97C \uC870\uD68C\uD558\uACE0, DDL\uC744 \uC0DD\uC131\uD558\uBA70, \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD\uC744 \uC81C\uC548\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD575\uC2EC \uAC1C\uB150: \uB2E4\uC774\uC5B4\uADF8\uB7A8(ERD \uBB38\uC11C), \uC5D4\uD130\uD2F0(\uD14C\uC774\uBE14, \uB17C\uB9AC\uBA85/\uBB3C\uB9AC\uBA85), \uCEEC\uB7FC(\uD0C0\uC785/PK/FK/Nullable), \uAD00\uACC4(1:1/1:N/M:N), \uC8FC\uC81C\uC601\uC5ED(\uC5C5\uBB34 \uB3C4\uBA54\uC778\uBCC4 \uBD84\uB958), \uD504\uB85C\uC81D\uD2B8(\uCD5C\uC0C1\uC704 \uB2E8\uC704), \uD45C\uC900\uC0AC\uC804(\uB17C\uB9AC\u2194\uBB3C\uB9AC \uB9E4\uD551). \uC0AC\uC6A9\uC790\uAC00 "\uC2A4\uD0A4\uB9C8/\uD14C\uC774\uBE14/ERD/\uC5D4\uD130\uD2F0/DDL"\uC744 \uC5B8\uAE09\uD558\uBA74 \uC774 \uC11C\uBC84\uC758 \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uCF54\uB4DC \uC0DD\uC131 \uC694\uCCAD \uC2DC \uBA3C\uC800 \uC2A4\uD0A4\uB9C8\uB97C \uC870\uD68C\uD55C \uD6C4 \uC0DD\uC131\uD558\uC138\uC694. \uBCC0\uACBD \uC81C\uC548\uC740 propose_schema_change\uB85C Draft \uC804\uC1A1 \u2192 \uC0AC\uC6A9\uC790 \uC2B9\uC778 \uD544\uC694.'
22429
+ description: SERVER_DESCRIPTION
22567
22430
  });
22568
22431
  registerResources(server, client);
22569
22432
  registerTools(server, client);
@@ -22571,7 +22434,18 @@ async function main() {
22571
22434
  await server.connect(transport);
22572
22435
  console.error("[ThinkERD MCP] Server started. Connected via stdio.");
22573
22436
  }
22574
- main().catch((err) => {
22437
+
22438
+ // src/index.ts
22439
+ var argv = process.argv.slice(2);
22440
+ var read = (flag) => {
22441
+ const i = argv.indexOf(flag);
22442
+ return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
22443
+ };
22444
+ mcpCommand({
22445
+ apiUrl: read("--api-url"),
22446
+ token: read("--token"),
22447
+ dev: argv.includes("--dev")
22448
+ }).catch((err) => {
22575
22449
  console.error("[ThinkERD MCP] Fatal error:", err);
22576
22450
  process.exit(1);
22577
22451
  });