@sophia-vibelog/mcp-server 0.3.2 → 0.4.1

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 (3) hide show
  1. package/README.md +4 -4
  2. package/dist/index.js +85 -39
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -69,10 +69,10 @@ AI 코딩 세션 로그를 Sophia에 발행합니다.
69
69
 
70
70
  **사용법:**
71
71
 
72
- 질문 끝에 `@@`를 붙이면 소피아가 질문 코칭을 제공합니다:
72
+ 질문 끝에 `??`를 붙이면 소피아가 질문 코칭을 제공합니다:
73
73
 
74
74
  ```
75
- 사용자: "에러가 난다@@"
75
+ 사용자: "에러가 난다??"
76
76
 
77
77
  → Claude의 답변
78
78
  ...
@@ -93,11 +93,11 @@ AI 코딩 세션 로그를 Sophia에 발행합니다.
93
93
  ```
94
94
 
95
95
  **매개변수:**
96
- - `userMessage` (필수) — 평가할 사용자 메시지 (`@@`는 제거된 상태)
96
+ - `userMessage` (필수) — 평가할 사용자 메시지 (`??`는 제거된 상태)
97
97
  - `conversationContext` (선택) — 대화 맥락
98
98
 
99
99
  **특징:**
100
- - 👓 `@@` = 안경 쓰고 자세히 보기
100
+ - `??` = 질문을 잘하고 싶을 때
101
101
  - 💬 격려 중심 코칭 (점수/비판 없음)
102
102
  - 📍 답변 맨 마지막에 표시 (긴 스크롤 고려)
103
103
 
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,35 +96,27 @@ 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
  }
76
109
  });
77
- server.tool("rate-prompt", "사용자의 질문 품질을 평가하고 개선 제안을 제공합니다. 질문 끝에 @@가 붙어있을 때만 사용하세요. 중요: 이 도구를 호출한 후, 반환된 텍스트를 반드시 당신의 응답 본문 텍스트에 직접 포함시켜 출력하세요. 도구 결과 블록에만 남겨두면 사용자가 볼 수 없습니다. 응답의 가장 마지막에 --- 구분선 아래, 원본 포맷 그대로(━━━ 테두리, 들여쓰기, 이모지 포함) 복사해서 출력하세요. 재가공, 요약, 마크다운 변환을 하지 마세요.", {
78
- userMessage: z.string().describe("평가할 사용자 메시지 (@@는 제거된 상태)"),
110
+ server.tool("rate-prompt", "사용자의 질문 품질을 평가하고 개선 제안을 제공합니다. 질문 끝에 ??가 붙어있을 때만 사용하세요. 중요: 이 도구를 호출한 후, 반환된 텍스트를 반드시 당신의 응답 본문 텍스트에 직접 포함시켜 출력하세요. 도구 결과 블록에만 남겨두면 사용자가 볼 수 없습니다. 응답의 가장 마지막에 --- 구분선 아래, 원본 포맷 그대로(━━━ 테두리, 들여쓰기, 이모지 포함) 복사해서 출력하세요. 재가공, 요약, 마크다운 변환을 하지 마세요.", {
111
+ userMessage: z.string().describe("평가할 사용자 메시지 (??는 제거된 상태)"),
79
112
  conversationContext: z
80
113
  .string()
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,12 +1,12 @@
1
1
  {
2
2
  "name": "@sophia-vibelog/mcp-server",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "Sophia (VibeLog) MCP Server — Claude Code에서 AI 코딩 세션 로그를 자동 발행합니다",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "sophia-mcp": "dist/index.js"
9
+ "mcp-server": "dist/index.js"
10
10
  },
11
11
  "files": [
12
12
  "dist",
@@ -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
  },