@crmforall/connector 0.1.2 → 0.1.4

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.
@@ -16,6 +16,8 @@ export declare const MCP_TOOL: {
16
16
  readonly IMPORT_PROSPECTS: "import_prospects";
17
17
  readonly REGISTER_ENTRY_POINT: "register_entry_point";
18
18
  readonly TRACK_ENGAGEMENT: "track_engagement";
19
+ readonly ONBOARDING_STATUS: "onboarding_status";
20
+ readonly SYNC_ALL: "sync_all";
19
21
  };
20
22
  export type McpToolName = (typeof MCP_TOOL)[keyof typeof MCP_TOOL];
21
23
  export declare const APPROVAL_SCOPE: {
package/dist/contracts.js CHANGED
@@ -19,6 +19,8 @@ exports.MCP_TOOL = {
19
19
  IMPORT_PROSPECTS: "import_prospects",
20
20
  REGISTER_ENTRY_POINT: "register_entry_point",
21
21
  TRACK_ENGAGEMENT: "track_engagement",
22
+ ONBOARDING_STATUS: "onboarding_status",
23
+ SYNC_ALL: "sync_all",
22
24
  };
23
25
  exports.APPROVAL_SCOPE = {
24
26
  TARGET_SNAPSHOT_CREATE: "target_snapshot:create",
@@ -41,6 +43,8 @@ exports.TOOL_APPROVAL_REQUIREMENT = {
41
43
  [exports.MCP_TOOL.IMPORT_PROSPECTS]: exports.APPROVAL_SCOPE.PROSPECT_IMPORT,
42
44
  [exports.MCP_TOOL.REGISTER_ENTRY_POINT]: exports.APPROVAL_SCOPE.ENTRY_POINT_REGISTER,
43
45
  [exports.MCP_TOOL.TRACK_ENGAGEMENT]: null,
46
+ [exports.MCP_TOOL.ONBOARDING_STATUS]: null,
47
+ [exports.MCP_TOOL.SYNC_ALL]: null,
44
48
  };
45
49
  /** 승인 토큰이 필요한 도구 — 토큰 없이 호출되면 즉시 거부한다. */
46
50
  exports.APPROVAL_REQUIRED_TOOLS = new Set(Object.entries(exports.TOOL_APPROVAL_REQUIREMENT)
package/dist/index.d.ts CHANGED
@@ -6,6 +6,9 @@ export * from "./mapping";
6
6
  export * from "./platform";
7
7
  export * from "./targets";
8
8
  export * from "./tools";
9
+ export * from "./sync";
10
+ export { PostgresAdapter } from "./db/postgres";
11
+ export type { DbAdapter } from "./db/adapter";
9
12
  /**
10
13
  * crmforall MCP 커넥터 라이브러리 진입점.
11
14
  * 서버 기동은 server.ts(bin)에서 — CLI가 이 모듈을 import해도 서버가 뜨지 않는다.
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.PostgresAdapter = void 0;
17
18
  exports.buildAdapter = buildAdapter;
18
19
  const postgres_1 = require("./db/postgres");
19
20
  __exportStar(require("./config"), exports);
@@ -22,6 +23,9 @@ __exportStar(require("./mapping"), exports);
22
23
  __exportStar(require("./platform"), exports);
23
24
  __exportStar(require("./targets"), exports);
24
25
  __exportStar(require("./tools"), exports);
26
+ __exportStar(require("./sync"), exports);
27
+ var postgres_2 = require("./db/postgres");
28
+ Object.defineProperty(exports, "PostgresAdapter", { enumerable: true, get: function () { return postgres_2.PostgresAdapter; } });
25
29
  /**
26
30
  * crmforall MCP 커넥터 라이브러리 진입점.
27
31
  * 서버 기동은 server.ts(bin)에서 — CLI가 이 모듈을 import해도 서버가 뜨지 않는다.
package/dist/server.js CHANGED
@@ -30,6 +30,14 @@ async function main() {
30
30
  const server = new mcp_js_1.McpServer({
31
31
  name: "crmforall-connector",
32
32
  version: "0.1.0",
33
+ }, {
34
+ instructions: [
35
+ "크렘포올 DB 커넥터입니다. 고객사 DB를 진단·연결·매핑해 크렘포올 플랫폼과 잇습니다.",
36
+ "온보딩 순서: onboarding_status로 현재 단계를 확인 → test_connection(연결·권한) → inspect_schema(컬럼 확인) → propose_field_mapping(후보) → validate_mapping(persist=true)으로 확정.",
37
+ "막히면 onboarding_status의 nextStep을 따르세요. 오류 응답의 hint도 다음 행동을 알려줍니다.",
38
+ "보안 원칙: DB 비밀번호·커넥터 시크릿·고객 개인정보(전화번호 원문 등)는 절대 사용자에게 출력하지 마세요. 컬럼명·집계·인원수만 다룹니다.",
39
+ "쓰기/발송 도구(create_target_snapshot·send_kakao_message 등)는 플랫폼의 사람 승인 토큰이 있어야 동작합니다. 승인 없이는 거부됩니다.",
40
+ ].join("\n"),
33
41
  });
34
42
  (0, tools_1.registerTools)(server, {
35
43
  config,
package/dist/sync.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { DbAdapter } from "./db/adapter";
2
+ import type { PlatformClient } from "./platform";
3
+ /**
4
+ * 발송 결과 동기화 — 플랫폼이 가진 발송 작업 결과를 고객사 CRM 스키마로 반영한다.
5
+ * 데이터 방향: 플랫폼 → 고객사 DB(crm_send_jobs / crm_send_results). 승인 불필요(읽기성).
6
+ * CLI `connect sync`(주기 폴링)와 MCP `sync_send_result`·`sync_all`이 공유한다.
7
+ */
8
+ export interface SyncedJob {
9
+ sendJobId: string;
10
+ status: string;
11
+ counts: Record<string, number>;
12
+ }
13
+ /** 한 발송 작업의 결과를 플랫폼에서 가져와 CRM 스키마에 upsert */
14
+ export declare function syncSendJob(db: DbAdapter, platform: PlatformClient, crmSchema: string, sendJobId: string): Promise<SyncedJob>;
15
+ export interface SyncResult {
16
+ synced: number;
17
+ /** 아직 진행 중(non-terminal)으로 남은 작업 수 — 다음 주기에 다시 시도 */
18
+ stillPending: number;
19
+ jobs: SyncedJob[];
20
+ }
21
+ /**
22
+ * 진행 중인(완료/실패가 아닌) 발송 작업을 모두 동기화한다 (주기 폴링의 1회분).
23
+ * 데이터 방향은 단방향(플랫폼→로컬)이라 원본 운영 테이블은 절대 건드리지 않는다.
24
+ */
25
+ export declare function syncPendingSendJobs(opts: {
26
+ db: DbAdapter;
27
+ platform: PlatformClient;
28
+ crmSchema: string;
29
+ limit?: number;
30
+ }): Promise<SyncResult>;
package/dist/sync.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.syncSendJob = syncSendJob;
4
+ exports.syncPendingSendJobs = syncPendingSendJobs;
5
+ const TERMINAL = new Set(["done", "failed"]);
6
+ /** 한 발송 작업의 결과를 플랫폼에서 가져와 CRM 스키마에 upsert */
7
+ async function syncSendJob(db, platform, crmSchema, sendJobId) {
8
+ const job = await platform.getSendJob(sendJobId);
9
+ for (const r of job.results) {
10
+ await db.execute(`INSERT INTO ${crmSchema}.crm_send_results
11
+ (send_job_id, receiver_ref, status, provider_code, internal_code)
12
+ VALUES ($1, $2, $3, $4, $5)
13
+ ON CONFLICT (send_job_id, receiver_ref) DO UPDATE
14
+ SET status = EXCLUDED.status,
15
+ provider_code = EXCLUDED.provider_code,
16
+ internal_code = EXCLUDED.internal_code,
17
+ synced_at = now()`, [sendJobId, r.receiverRef, r.status, r.providerCode, r.internalCode]);
18
+ }
19
+ await db.execute(`UPDATE ${crmSchema}.crm_send_jobs SET status = $2 WHERE send_job_id = $1`, [sendJobId, job.status]);
20
+ const counts = job.results.reduce((acc, r) => {
21
+ acc[r.status] = (acc[r.status] ?? 0) + 1;
22
+ return acc;
23
+ }, {});
24
+ return { sendJobId, status: job.status, counts };
25
+ }
26
+ /**
27
+ * 진행 중인(완료/실패가 아닌) 발송 작업을 모두 동기화한다 (주기 폴링의 1회분).
28
+ * 데이터 방향은 단방향(플랫폼→로컬)이라 원본 운영 테이블은 절대 건드리지 않는다.
29
+ */
30
+ async function syncPendingSendJobs(opts) {
31
+ const { db, platform, crmSchema, limit = 200 } = opts;
32
+ const rows = await db.selectRows(`SELECT send_job_id FROM ${crmSchema}.crm_send_jobs
33
+ WHERE status NOT IN ('done','failed')
34
+ ORDER BY send_job_id
35
+ LIMIT $1`, [limit]);
36
+ const jobs = [];
37
+ let stillPending = 0;
38
+ for (const row of rows) {
39
+ const id = String(row.send_job_id);
40
+ const synced = await syncSendJob(db, platform, crmSchema, id);
41
+ jobs.push(synced);
42
+ if (!TERMINAL.has(synced.status))
43
+ stillPending++;
44
+ }
45
+ return { synced: jobs.length, stillPending, jobs };
46
+ }
package/dist/tools.js CHANGED
@@ -7,6 +7,7 @@ const contracts_1 = require("./contracts");
7
7
  const config_1 = require("./config");
8
8
  const mapping_1 = require("./mapping");
9
9
  const targets_1 = require("./targets");
10
+ const sync_1 = require("./sync");
10
11
  function ok(data) {
11
12
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
12
13
  }
@@ -73,6 +74,48 @@ function requireMapping(rt, mappingVersion) {
73
74
  }
74
75
  function registerTools(server, rt) {
75
76
  const s = rt.config.crmSchema;
77
+ // AI 온보딩 안내 — 지금 어디까지 됐고 다음에 뭘 할지 한 번에 알려준다.
78
+ server.tool(contracts_1.MCP_TOOL.ONBOARDING_STATUS, "온보딩 진행 상태와 다음 할 일을 반환한다. 연결 직후·중간에 호출해 흐름을 안내받는다 (승인 불필요).", {}, async () => {
79
+ const steps = [];
80
+ // 1) 자격증명/설정
81
+ steps.push({ step: "register", done: true, detail: `connectionId=${rt.config.connectionId}` });
82
+ // 2) DB 연결·권한
83
+ let dbReady = false;
84
+ let overpriv = false;
85
+ try {
86
+ const db = await rt.getDb();
87
+ const report = await db.checkReadiness({ crmSchema: s });
88
+ dbReady = report.ready;
89
+ overpriv = report.overprivileged;
90
+ steps.push({
91
+ step: "db_connection",
92
+ done: report.ready && !report.overprivileged,
93
+ detail: report.overprivileged ? "과권한 계정 — 권한 축소 필요" : report.ready ? "연결·스키마 정상" : "준비 미완 (doctor 참고)",
94
+ });
95
+ }
96
+ catch (err) {
97
+ steps.push({ step: "db_connection", done: false, detail: err instanceof Error ? err.message : String(err) });
98
+ }
99
+ // 3) 필드 매핑
100
+ const mapped = rt.config.mapping !== null;
101
+ steps.push({ step: "field_mapping", done: mapped, detail: mapped ? "매핑 확정됨" : "propose_field_mapping → validate_mapping(persist=true) 필요" });
102
+ // 다음 할 일 한 줄
103
+ let nextStep;
104
+ if (overpriv)
105
+ nextStep = "DB 계정이 과권한입니다. test_connection의 권한 축소 SQL을 DBA에게 전달해 권한을 낮춘 뒤 다시 시도하세요.";
106
+ else if (!dbReady)
107
+ nextStep = "DB 연결/스키마가 준비되지 않았습니다. test_connection으로 원인을 확인하세요.";
108
+ else if (!mapped)
109
+ nextStep = "inspect_schema로 컬럼을 본 뒤, propose_field_mapping으로 후보를 만들고 validate_mapping(persist=true)으로 확정하세요.";
110
+ else
111
+ nextStep = "온보딩 완료! preview_targets로 대상을 미리 보거나, 실시간 동기화를 켜세요.";
112
+ return ok({
113
+ ready: dbReady && !overpriv && mapped,
114
+ steps,
115
+ nextStep,
116
+ guidance: "이 순서로 진행하세요: test_connection → inspect_schema → propose_field_mapping → validate_mapping(persist=true) → (완료) preview_targets. 자격증명·비밀번호는 절대 출력하지 마세요.",
117
+ });
118
+ });
76
119
  server.tool(contracts_1.MCP_TOOL.TEST_CONNECTION, "DB 연결 상태와 권한 요약을 반환한다 (승인 불필요, 민감정보 미노출)", {}, async () => {
77
120
  try {
78
121
  const db = await rt.getDb();
@@ -316,24 +359,19 @@ function registerTools(server, rt) {
316
359
  });
317
360
  server.tool(contracts_1.MCP_TOOL.SYNC_SEND_RESULT, "발송 작업 결과를 플랫폼에서 가져와 CRM 스키마에 동기화한다 (승인 불필요)", { sendJobId: zod_1.z.string() }, async ({ sendJobId }) => {
318
361
  try {
319
- const job = await rt.platform.getSendJob(sendJobId);
320
362
  const db = await rt.getDb();
321
- for (const r of job.results) {
322
- await db.execute(`INSERT INTO ${s}.crm_send_results
323
- (send_job_id, receiver_ref, status, provider_code, internal_code)
324
- VALUES ($1, $2, $3, $4, $5)
325
- ON CONFLICT (send_job_id, receiver_ref) DO UPDATE
326
- SET status = EXCLUDED.status,
327
- provider_code = EXCLUDED.provider_code,
328
- internal_code = EXCLUDED.internal_code,
329
- synced_at = now()`, [sendJobId, r.receiverRef, r.status, r.providerCode, r.internalCode]);
330
- }
331
- await db.execute(`UPDATE ${s}.crm_send_jobs SET status = $2 WHERE send_job_id = $1`, [sendJobId, job.status]);
332
- const counts = job.results.reduce((acc, r) => {
333
- acc[r.status] = (acc[r.status] ?? 0) + 1;
334
- return acc;
335
- }, {});
336
- return ok({ sendJobId, status: job.status, resultCounts: counts });
363
+ const r = await (0, sync_1.syncSendJob)(db, rt.platform, s, sendJobId);
364
+ return ok({ sendJobId: r.sendJobId, status: r.status, resultCounts: r.counts });
365
+ }
366
+ catch (err) {
367
+ return unexpected(err);
368
+ }
369
+ });
370
+ server.tool(contracts_1.MCP_TOOL.SYNC_ALL, "진행 중인 발송 작업 전체를 한 번에 동기화한다 (주기 폴링 1회분, 승인 불필요)", { limit: zod_1.z.number().int().positive().max(500).optional() }, async ({ limit }) => {
371
+ try {
372
+ const db = await rt.getDb();
373
+ const result = await (0, sync_1.syncPendingSendJobs)({ db, platform: rt.platform, crmSchema: s, limit });
374
+ return ok(result);
337
375
  }
338
376
  catch (err) {
339
377
  return unexpected(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crmforall/connector",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "crmforall MCP 커넥터 — 운영 DB 읽기, CRM 스키마 쓰기(승인 토큰 필수), 대상 산출",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",