@sophia-vibelog/mcp-server 0.3.2 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +83 -37
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,29 +1,61 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
5
  import { z } from "zod";
5
- const SOPHIA_API_URL = process.env.SOPHIA_API_URL || "http://localhost:3000";
6
+ // ── 버전 (package.json에서 동적 로드) ──
7
+ const require = createRequire(import.meta.url);
8
+ const { version: PKG_VERSION } = require("../package.json");
9
+ // ── 프로세스 레벨 에러 핸들러 (복구 없이 로깅 + 종료) ──
10
+ process.on("uncaughtException", (err) => {
11
+ console.error("[sophia-mcp] uncaughtException:", err.message);
12
+ process.exit(1);
13
+ });
14
+ process.on("unhandledRejection", (reason) => {
15
+ console.error("[sophia-mcp] unhandledRejection:", reason);
16
+ process.exit(1);
17
+ });
18
+ // ── env 검증 (P0: 미설정 시 즉시 실패) ──
19
+ const SOPHIA_API_URL = process.env.SOPHIA_API_URL || "";
6
20
  const SOPHIA_API_KEY = process.env.SOPHIA_API_KEY || "";
21
+ const FETCH_TIMEOUT_MS = 30_000;
22
+ if (!SOPHIA_API_URL) {
23
+ console.error("[sophia-mcp] SOPHIA_API_URL이 설정되지 않았습니다. MCP config의 env에 설정해주세요.");
24
+ }
25
+ if (!SOPHIA_API_KEY) {
26
+ console.error("[sophia-mcp] SOPHIA_API_KEY가 설정되지 않았습니다. MCP config의 env에 설정해주세요.");
27
+ }
28
+ /** env 미설정 시 도구 호출에서 반환할 에러 */
29
+ function envError() {
30
+ const missing = [];
31
+ if (!SOPHIA_API_URL)
32
+ missing.push("SOPHIA_API_URL");
33
+ if (!SOPHIA_API_KEY)
34
+ missing.push("SOPHIA_API_KEY");
35
+ if (missing.length === 0)
36
+ return null;
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: `환경변수가 설정되지 않았습니다: ${missing.join(", ")}.\nMCP config의 env 블록에 추가해주세요.\n예: "env": { "SOPHIA_API_KEY": "sk-...", "SOPHIA_API_URL": "https://..." }`,
42
+ },
43
+ ],
44
+ isError: true,
45
+ };
46
+ }
7
47
  const server = new McpServer({
8
48
  name: "sophia-mcp",
9
- version: "0.3.0",
49
+ version: PKG_VERSION,
10
50
  });
11
51
  server.tool("publish-log", "VibeLog에 AI 코딩 세션 로그를 발행합니다. 세션 로그 텍스트를 전달하면 AI가 분석하고 소피아가 조언을 남깁니다.", {
12
52
  rawSessionLog: z.string().describe("AI 코딩 세션의 전체 로그 텍스트"),
13
53
  title: z.string().optional().describe("로그 제목 (선택)"),
14
54
  projectId: z.string().optional().describe("프로젝트 ID (선택)"),
15
55
  }, async ({ rawSessionLog, title, projectId }) => {
16
- if (!SOPHIA_API_KEY) {
17
- return {
18
- content: [
19
- {
20
- type: "text",
21
- text: "SOPHIA_API_KEY가 설정되지 않았습니다. VibeLog 설정에서 API 키를 생성한 후 환경변수에 설정해주세요.",
22
- },
23
- ],
24
- isError: true,
25
- };
26
- }
56
+ const env = envError();
57
+ if (env)
58
+ return env;
27
59
  try {
28
60
  const response = await fetch(`${SOPHIA_API_URL}/api/logs`, {
29
61
  method: "POST",
@@ -36,6 +68,7 @@ server.tool("publish-log", "VibeLog에 AI 코딩 세션 로그를 발행합니
36
68
  title: title || undefined,
37
69
  projectId: projectId || undefined,
38
70
  }),
71
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
39
72
  });
40
73
  if (!response.ok) {
41
74
  const error = await response.json().catch(() => ({}));
@@ -63,13 +96,13 @@ server.tool("publish-log", "VibeLog에 AI 코딩 세션 로그를 발행합니
63
96
  };
64
97
  }
65
98
  catch (e) {
99
+ const isTimeout = e instanceof DOMException && e.name === "TimeoutError";
100
+ const msg = isTimeout
101
+ ? `VibeLog 서버 응답 없음 (${FETCH_TIMEOUT_MS / 1000}초 타임아웃). 서버 상태를 확인해주세요: ${SOPHIA_API_URL}`
102
+ : `VibeLog 서버 연결 실패: ${e instanceof Error ? e.message : "알 수 없는 오류"}. SOPHIA_API_URL을 확인해주세요.`;
103
+ console.error(`[sophia-mcp] publish-log: ${msg}`);
66
104
  return {
67
- content: [
68
- {
69
- type: "text",
70
- text: `VibeLog 서버 연결 실패: ${e instanceof Error ? e.message : "알 수 없는 오류"}. SOPHIA_API_URL을 확인해주세요.`,
71
- },
72
- ],
105
+ content: [{ type: "text", text: msg }],
73
106
  isError: true,
74
107
  };
75
108
  }
@@ -81,17 +114,9 @@ server.tool("rate-prompt", "사용자의 질문 품질을 평가하고 개선
81
114
  .optional()
82
115
  .describe("대화 맥락 (선택사항)"),
83
116
  }, async ({ userMessage, conversationContext }) => {
84
- if (!SOPHIA_API_KEY) {
85
- return {
86
- content: [
87
- {
88
- type: "text",
89
- text: "SOPHIA_API_KEY가 설정되지 않았습니다. VibeLog 설정에서 API 키를 생성한 후 환경변수에 설정해주세요.",
90
- },
91
- ],
92
- isError: true,
93
- };
94
- }
117
+ const env = envError();
118
+ if (env)
119
+ return env;
95
120
  try {
96
121
  const response = await fetch(`${SOPHIA_API_URL}/api/rate-prompt`, {
97
122
  method: "POST",
@@ -103,6 +128,7 @@ server.tool("rate-prompt", "사용자의 질문 품질을 평가하고 개선
103
128
  userMessage,
104
129
  conversationContext: conversationContext || undefined,
105
130
  }),
131
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
106
132
  });
107
133
  if (!response.ok) {
108
134
  const error = await response.json().catch(() => ({}));
@@ -116,7 +142,27 @@ server.tool("rate-prompt", "사용자의 질문 품질을 평가하고 개선
116
142
  isError: true,
117
143
  };
118
144
  }
119
- const result = await response.json();
145
+ const CoachingResultSchema = z.object({
146
+ encouragement: z.string(),
147
+ strengths: z.array(z.string()),
148
+ improvements: z.array(z.string()),
149
+ example: z.string(),
150
+ });
151
+ const raw = await response.json();
152
+ const parsed = CoachingResultSchema.safeParse(raw);
153
+ if (!parsed.success) {
154
+ console.error("[sophia-mcp] rate-prompt: 응답 스키마 불일치:", parsed.error.message);
155
+ return {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: "프롬프트 평가 결과를 처리할 수 없습니다. 서버 응답 형식이 예상과 다릅니다.",
160
+ },
161
+ ],
162
+ isError: true,
163
+ };
164
+ }
165
+ const result = parsed.data;
120
166
  // 피드백 포맷팅
121
167
  let feedback = `\n━━━━━━━━━━━━━━━━━━━━━━━\n`;
122
168
  feedback += `💬 소피아의 질문 코칭\n\n`;
@@ -145,13 +191,13 @@ server.tool("rate-prompt", "사용자의 질문 품질을 평가하고 개선
145
191
  };
146
192
  }
147
193
  catch (e) {
194
+ const isTimeout = e instanceof DOMException && e.name === "TimeoutError";
195
+ const msg = isTimeout
196
+ ? `프롬프트 평가 서버 응답 없음 (${FETCH_TIMEOUT_MS / 1000}초 타임아웃). 서버 상태를 확인해주세요: ${SOPHIA_API_URL}`
197
+ : `프롬프트 평가 실패: ${e instanceof Error ? e.message : "알 수 없는 오류"}`;
198
+ console.error(`[sophia-mcp] rate-prompt: ${msg}`);
148
199
  return {
149
- content: [
150
- {
151
- type: "text",
152
- text: `프롬프트 평가 실패: ${e instanceof Error ? e.message : "알 수 없는 오류"}`,
153
- },
154
- ],
200
+ content: [{ type: "text", text: msg }],
155
201
  isError: true,
156
202
  };
157
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sophia-vibelog/mcp-server",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Sophia (VibeLog) MCP Server — Claude Code에서 AI 코딩 세션 로그를 자동 발행합니다",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,6 +26,7 @@
26
26
  "scripts": {
27
27
  "build": "tsc",
28
28
  "dev": "tsc --watch",
29
+ "test:smoke": "node test/smoke.mjs",
29
30
  "prepublishOnly": "npm run build",
30
31
  "lint": "echo 'no lint configured'"
31
32
  },