@zelklab/seevo-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # Seevo MCP Server
2
+
3
+ Claude CodeからSeevoバックエンドに直接プロジェクトを登録するためのMCPサーバーです。
4
+
5
+ ## 機能
6
+
7
+ - **プロジェクト自動登録**: プロジェクトディレクトリを解析してSeevoに自動登録
8
+ - **セキュアな認証**: ログイントークンを安全に保存・管理
9
+ - **自動解析**: README.md、package.json等からサービス情報を自動抽出
10
+
11
+ ## セットアップ
12
+
13
+ ### 1. 依存関係のインストール
14
+
15
+ ```bash
16
+ cd /Users/sugimoto/Desktop/seevo/mcp-server
17
+ npm install
18
+ ```
19
+
20
+ ### 2. ビルド
21
+
22
+ ```bash
23
+ npm run build
24
+ ```
25
+
26
+ ### 3. Claude Code設定
27
+
28
+ `~/.config/claude-code/config.json` に以下を追加:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "seevo": {
34
+ "command": "node",
35
+ "args": ["/Users/sugimoto/Desktop/seevo/mcp-server/dist/index.js"]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### 4. Claude Codeを再起動
42
+
43
+ 設定を反映するためにClaude Codeを再起動してください。
44
+
45
+ ## 使い方
46
+
47
+ ### 1. ログイン
48
+
49
+ まずSeevoにログインします:
50
+
51
+ ```
52
+ User: Seevoにログインして
53
+ Claude Code: [seevo_login ツールを使用]
54
+ ```
55
+
56
+ または直接:
57
+
58
+ ```
59
+ User: メール test@example.com、パスワード password123 でSeevoにログインして
60
+ ```
61
+
62
+ ### 2. プロジェクト登録
63
+
64
+ 現在のプロジェクトをSeevoに登録:
65
+
66
+ ```
67
+ User: このプロジェクトをSeevoに登録して
68
+ Claude Code: [プロジェクトを解析してseevo_register_project ツールを使用]
69
+ ```
70
+
71
+ 特定のディレクトリを指定:
72
+
73
+ ```
74
+ User: /Users/sugimoto/Desktop/myproject をSeevoに登録して
75
+ ```
76
+
77
+ ### 3. ログアウト
78
+
79
+ ```
80
+ User: Seevoからログアウトして
81
+ ```
82
+
83
+ ## 利用可能なツール
84
+
85
+ ### `seevo_login`
86
+ - Seevoにログインして認証トークンを保存
87
+ - パラメータ: `email`, `password`
88
+
89
+ ### `seevo_register_project`
90
+ - プロジェクトをSeevoに登録
91
+ - パラメータ:
92
+ - `project_path` (省略可): プロジェクトディレクトリのパス
93
+ - `name` (省略可): サービス名
94
+
95
+ ### `seevo_logout`
96
+ - ログアウトして認証トークンを削除
97
+
98
+ ## プロジェクト解析
99
+
100
+ MCPサーバーは以下のファイルから自動的に情報を抽出します:
101
+
102
+ - **プロジェクト名**: `package.json`, `go.mod`, `Cargo.toml`, またはディレクトリ名
103
+ - **説明**: `README.md` の最初の段落、または `package.json` の description
104
+ - **ターゲットユーザー**: 技術スタックとキーワードから推測
105
+
106
+ ## セキュリティ
107
+
108
+ - 認証トークンは `~/.seevo/auth.json` に保存(権限600)
109
+ - トークンは7日間有効
110
+ - MCPサーバー経由のみでAPIにアクセス可能
111
+
112
+ ## トラブルシューティング
113
+
114
+ ### MCPサーバーが起動しない
115
+
116
+ ```bash
117
+ # ビルドエラーがないか確認
118
+ cd /Users/sugimoto/Desktop/seevo/mcp-server
119
+ npm run build
120
+
121
+ # 直接実行してエラーを確認
122
+ node dist/index.js
123
+ ```
124
+
125
+ ### 認証エラー
126
+
127
+ ```bash
128
+ # 認証情報をクリア
129
+ rm ~/.seevo/auth.json
130
+
131
+ # 再度ログイン
132
+ ```
133
+
134
+ ### Goバックエンドに接続できない
135
+
136
+ - Goバックエンドが起動しているか確認: `http://localhost:8080`
137
+ - 環境変数 `SEEVO_API_URL` で別のURLを指定可能
138
+
139
+ ## 開発
140
+
141
+ ```bash
142
+ # 開発モード(TypeScriptをビルドして実行)
143
+ npm run dev
144
+
145
+ # TypeScriptをビルド
146
+ npm run build
147
+
148
+ # 実行
149
+ npm start
150
+ ```
151
+
152
+ ## ライセンス
153
+
154
+ MIT
@@ -0,0 +1,269 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join, basename } from "path";
3
+ /**
4
+ * プロジェクトディレクトリを解析してサービス情報を抽出
5
+ */
6
+ export async function analyzeProject(projectPath, forcedName) {
7
+ // プロジェクト名を決定
8
+ const service_name = forcedName || detectProjectName(projectPath);
9
+ // README.mdから説明を抽出
10
+ const description = extractDescriptionFromReadme(projectPath) ||
11
+ extractDescriptionFromPackageJson(projectPath) ||
12
+ `${service_name}のマーケティングコンテンツ`;
13
+ // ターゲットオーディエンスを推測
14
+ const target = detectTargetAudience(projectPath);
15
+ // 技術スタックを検出
16
+ const tech_stack = detectTechStack(projectPath);
17
+ // 機能を検出(README.mdから抽出)
18
+ const features = detectFeatures(projectPath);
19
+ return {
20
+ service_name,
21
+ description,
22
+ target,
23
+ tech_stack: tech_stack.length > 0 ? tech_stack : undefined,
24
+ features: features.length > 0 ? features : undefined,
25
+ };
26
+ }
27
+ /**
28
+ * プロジェクト名を検出
29
+ */
30
+ function detectProjectName(projectPath) {
31
+ // package.json から取得
32
+ const packageJsonPath = join(projectPath, "package.json");
33
+ if (existsSync(packageJsonPath)) {
34
+ try {
35
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
36
+ if (packageJson.name) {
37
+ return packageJson.name;
38
+ }
39
+ }
40
+ catch (error) {
41
+ // Ignore
42
+ }
43
+ }
44
+ // go.mod から取得
45
+ const goModPath = join(projectPath, "go.mod");
46
+ if (existsSync(goModPath)) {
47
+ try {
48
+ const goMod = readFileSync(goModPath, "utf-8");
49
+ const match = goMod.match(/module\s+([^\s]+)/);
50
+ if (match && match[1]) {
51
+ const parts = match[1].split("/");
52
+ return parts[parts.length - 1];
53
+ }
54
+ }
55
+ catch (error) {
56
+ // Ignore
57
+ }
58
+ }
59
+ // Cargo.toml から取得
60
+ const cargoTomlPath = join(projectPath, "Cargo.toml");
61
+ if (existsSync(cargoTomlPath)) {
62
+ try {
63
+ const cargoToml = readFileSync(cargoTomlPath, "utf-8");
64
+ const match = cargoToml.match(/name\s*=\s*"([^"]+)"/);
65
+ if (match && match[1]) {
66
+ return match[1];
67
+ }
68
+ }
69
+ catch (error) {
70
+ // Ignore
71
+ }
72
+ }
73
+ // ディレクトリ名をフォールバック
74
+ return basename(projectPath);
75
+ }
76
+ /**
77
+ * README.mdから説明を抽出
78
+ */
79
+ function extractDescriptionFromReadme(projectPath) {
80
+ const readmePaths = [
81
+ join(projectPath, "README.md"),
82
+ join(projectPath, "readme.md"),
83
+ join(projectPath, "README"),
84
+ ];
85
+ for (const readmePath of readmePaths) {
86
+ if (existsSync(readmePath)) {
87
+ try {
88
+ const content = readFileSync(readmePath, "utf-8");
89
+ // タイトルの次の段落を抽出
90
+ const lines = content.split("\n");
91
+ let foundTitle = false;
92
+ let description = "";
93
+ for (const line of lines) {
94
+ if (line.startsWith("#")) {
95
+ foundTitle = true;
96
+ continue;
97
+ }
98
+ if (foundTitle && line.trim() !== "") {
99
+ description += line.trim() + " ";
100
+ if (description.length > 200) {
101
+ break;
102
+ }
103
+ }
104
+ }
105
+ if (description) {
106
+ return description.trim().substring(0, 500);
107
+ }
108
+ }
109
+ catch (error) {
110
+ // Ignore
111
+ }
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * package.jsonから説明を抽出
118
+ */
119
+ function extractDescriptionFromPackageJson(projectPath) {
120
+ const packageJsonPath = join(projectPath, "package.json");
121
+ if (existsSync(packageJsonPath)) {
122
+ try {
123
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
124
+ return packageJson.description || null;
125
+ }
126
+ catch (error) {
127
+ // Ignore
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * ターゲットオーディエンスを推測
134
+ */
135
+ function detectTargetAudience(projectPath) {
136
+ const techStack = detectTechStack(projectPath);
137
+ const keywords = detectKeywords(projectPath);
138
+ // 技術スタックベースの推測
139
+ if (techStack.includes("react") || techStack.includes("vue") || techStack.includes("next.js")) {
140
+ return "Web開発者、スタートアップ";
141
+ }
142
+ if (techStack.includes("go") && keywords.includes("api")) {
143
+ return "バックエンド開発者、API利用者";
144
+ }
145
+ if (techStack.includes("python") && (keywords.includes("ml") || keywords.includes("ai"))) {
146
+ return "データサイエンティスト、AI/ML エンジニア";
147
+ }
148
+ if (keywords.includes("cli") || keywords.includes("tool")) {
149
+ return "開発者、エンジニア";
150
+ }
151
+ // デフォルト
152
+ return "スタートアップの開発者、中小企業";
153
+ }
154
+ /**
155
+ * 技術スタックを検出
156
+ */
157
+ function detectTechStack(projectPath) {
158
+ const stack = [];
159
+ // package.json
160
+ if (existsSync(join(projectPath, "package.json"))) {
161
+ try {
162
+ const packageJson = JSON.parse(readFileSync(join(projectPath, "package.json"), "utf-8"));
163
+ const deps = {
164
+ ...packageJson.dependencies,
165
+ ...packageJson.devDependencies,
166
+ };
167
+ if (deps.next)
168
+ stack.push("Next.js");
169
+ else if (deps.react)
170
+ stack.push("React");
171
+ if (deps.vue)
172
+ stack.push("Vue");
173
+ if (deps.express)
174
+ stack.push("Express");
175
+ if (deps.typescript || deps["@types/node"])
176
+ stack.push("TypeScript");
177
+ if (deps.tailwindcss || deps["tailwindcss"])
178
+ stack.push("Tailwind CSS");
179
+ if (deps.postgresql || deps.pg)
180
+ stack.push("PostgreSQL");
181
+ }
182
+ catch (error) {
183
+ // Ignore
184
+ }
185
+ }
186
+ // go.mod
187
+ if (existsSync(join(projectPath, "go.mod"))) {
188
+ stack.push("Go");
189
+ }
190
+ // requirements.txt or setup.py
191
+ if (existsSync(join(projectPath, "requirements.txt")) ||
192
+ existsSync(join(projectPath, "setup.py"))) {
193
+ stack.push("Python");
194
+ }
195
+ // Cargo.toml
196
+ if (existsSync(join(projectPath, "Cargo.toml"))) {
197
+ stack.push("Rust");
198
+ }
199
+ // Docker
200
+ if (existsSync(join(projectPath, "Dockerfile")) || existsSync(join(projectPath, "docker-compose.yml"))) {
201
+ stack.push("Docker");
202
+ }
203
+ return stack;
204
+ }
205
+ /**
206
+ * キーワードを検出
207
+ */
208
+ function detectKeywords(projectPath) {
209
+ const keywords = [];
210
+ // README.mdからキーワード抽出
211
+ const readmePath = join(projectPath, "README.md");
212
+ if (existsSync(readmePath)) {
213
+ try {
214
+ const content = readFileSync(readmePath, "utf-8").toLowerCase();
215
+ if (content.includes("api"))
216
+ keywords.push("api");
217
+ if (content.includes("cli"))
218
+ keywords.push("cli");
219
+ if (content.includes("tool"))
220
+ keywords.push("tool");
221
+ if (content.includes("machine learning") || content.includes("ml"))
222
+ keywords.push("ml");
223
+ if (content.includes("artificial intelligence") || content.includes("ai"))
224
+ keywords.push("ai");
225
+ if (content.includes("web app") || content.includes("webapp"))
226
+ keywords.push("webapp");
227
+ }
228
+ catch (error) {
229
+ // Ignore
230
+ }
231
+ }
232
+ return keywords;
233
+ }
234
+ /**
235
+ * 機能を検出(README.mdから)
236
+ */
237
+ function detectFeatures(projectPath) {
238
+ const features = [];
239
+ const readmePath = join(projectPath, "README.md");
240
+ if (existsSync(readmePath)) {
241
+ try {
242
+ const content = readFileSync(readmePath, "utf-8");
243
+ const lines = content.split("\n");
244
+ let inFeaturesSection = false;
245
+ for (const line of lines) {
246
+ // 機能セクションの開始を検出
247
+ if (line.match(/^##?\s+(Features|機能|主な機能|Main Features)/i)) {
248
+ inFeaturesSection = true;
249
+ continue;
250
+ }
251
+ // 次のセクション見出しで終了
252
+ if (inFeaturesSection && line.match(/^##?\s+/)) {
253
+ break;
254
+ }
255
+ // リスト項目を抽出
256
+ if (inFeaturesSection) {
257
+ const listMatch = line.match(/^[\s]*[-*+]\s+(.+)/);
258
+ if (listMatch && listMatch[1]) {
259
+ features.push(listMatch[1].trim());
260
+ }
261
+ }
262
+ }
263
+ }
264
+ catch (error) {
265
+ // Ignore
266
+ }
267
+ }
268
+ return features.slice(0, 10); // 最大10個まで
269
+ }
package/dist/auth.js ADDED
@@ -0,0 +1,97 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ /**
5
+ * 認証トークン管理クラス
6
+ * ユーザーのホームディレクトリにMCPトークンを安全に保存
7
+ */
8
+ export class AuthManager {
9
+ configDir;
10
+ authFile;
11
+ authData = null;
12
+ constructor() {
13
+ this.configDir = join(homedir(), ".seevo");
14
+ this.authFile = join(this.configDir, "auth.json");
15
+ this.loadToken();
16
+ }
17
+ /**
18
+ * 認証トークンを読み込む
19
+ * 優先順位: 1. 環境変数 SEEVO_MCP_TOKEN, 2. ファイル ~/.seevo/auth.json
20
+ */
21
+ loadToken() {
22
+ try {
23
+ // 1. 環境変数から読み込み(Claude Code設定で渡される)
24
+ const envToken = process.env.SEEVO_MCP_TOKEN;
25
+ if (envToken) {
26
+ // 環境変数のトークンは30日間有効とみなす
27
+ this.authData = {
28
+ token: envToken,
29
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
30
+ };
31
+ console.error("Auth: Using token from environment variable");
32
+ return;
33
+ }
34
+ // 2. ファイルから読み込み
35
+ if (existsSync(this.authFile)) {
36
+ const data = readFileSync(this.authFile, "utf-8");
37
+ this.authData = JSON.parse(data);
38
+ // トークンが期限切れかチェック
39
+ if (this.authData && this.authData.expiresAt < Date.now()) {
40
+ this.authData = null;
41
+ }
42
+ else if (this.authData) {
43
+ console.error("Auth: Using token from file");
44
+ }
45
+ }
46
+ }
47
+ catch (error) {
48
+ console.error("Failed to load auth token:", error);
49
+ this.authData = null;
50
+ }
51
+ }
52
+ /**
53
+ * MCPトークンを保存 (30日間有効)
54
+ */
55
+ saveToken(token, expiresInDays = 30) {
56
+ try {
57
+ // ディレクトリが存在しない場合は作成
58
+ if (!existsSync(this.configDir)) {
59
+ mkdirSync(this.configDir, { recursive: true });
60
+ }
61
+ const expiresAt = Date.now() + expiresInDays * 24 * 60 * 60 * 1000;
62
+ this.authData = { token, expiresAt };
63
+ writeFileSync(this.authFile, JSON.stringify(this.authData, null, 2), {
64
+ mode: 0o600, // 所有者のみ読み書き可能
65
+ });
66
+ }
67
+ catch (error) {
68
+ throw new Error(`Failed to save auth token: ${error}`);
69
+ }
70
+ }
71
+ /**
72
+ * MCPトークンを取得
73
+ */
74
+ getToken() {
75
+ return this.authData?.token || null;
76
+ }
77
+ /**
78
+ * 認証済みかチェック
79
+ */
80
+ isAuthenticated() {
81
+ return this.authData !== null && this.authData.expiresAt > Date.now();
82
+ }
83
+ /**
84
+ * 認証トークンをクリア
85
+ */
86
+ clearToken() {
87
+ this.authData = null;
88
+ try {
89
+ if (existsSync(this.authFile)) {
90
+ writeFileSync(this.authFile, "");
91
+ }
92
+ }
93
+ catch (error) {
94
+ console.error("Failed to clear auth token:", error);
95
+ }
96
+ }
97
+ }
package/dist/client.js ADDED
@@ -0,0 +1,98 @@
1
+ import fetch from "node-fetch";
2
+ /**
3
+ * Seevoバックエンドとの通信クライアント
4
+ */
5
+ export class SeeveClient {
6
+ baseUrl;
7
+ authManager;
8
+ constructor(authManager) {
9
+ this.baseUrl = process.env.SEEVO_API_URL || "http://localhost:8080/api/v1";
10
+ this.authManager = authManager;
11
+ }
12
+ /**
13
+ * ログイン
14
+ */
15
+ async login(email, password) {
16
+ // Step 1: Login to get JWT token in cookie
17
+ const loginResponse = await fetch(`${this.baseUrl}/auth/login`, {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ },
22
+ body: JSON.stringify({ email, password }),
23
+ });
24
+ if (!loginResponse.ok) {
25
+ const error = await loginResponse.text();
26
+ throw new Error(`Login failed: ${error}`);
27
+ }
28
+ // Extract JWT token from cookie header
29
+ const setCookie = loginResponse.headers.get("set-cookie");
30
+ if (!setCookie) {
31
+ throw new Error("No authentication cookie received");
32
+ }
33
+ const tokenMatch = setCookie.match(/token=([^;]+)/);
34
+ if (!tokenMatch) {
35
+ throw new Error("Failed to extract authentication token");
36
+ }
37
+ const jwtToken = tokenMatch[1];
38
+ // Step 2: Request MCP token using the JWT token
39
+ const mcpTokenResponse = await fetch(`${this.baseUrl}/auth/mcp-token`, {
40
+ method: "POST",
41
+ headers: {
42
+ Cookie: `token=${jwtToken}`,
43
+ },
44
+ });
45
+ if (!mcpTokenResponse.ok) {
46
+ const error = await mcpTokenResponse.text();
47
+ throw new Error(`Failed to generate MCP token: ${error}`);
48
+ }
49
+ const mcpTokenData = (await mcpTokenResponse.json());
50
+ // Save the MCP token (not the JWT token)
51
+ this.authManager.saveToken(mcpTokenData.token);
52
+ const loginData = await loginResponse.json();
53
+ return loginData;
54
+ }
55
+ /**
56
+ * プロジェクトを登録
57
+ */
58
+ async registerProject(projectData) {
59
+ const token = this.authManager.getToken();
60
+ if (!token) {
61
+ throw new Error("Not authenticated. Please login first.");
62
+ }
63
+ const response = await fetch(`${this.baseUrl}/apps`, {
64
+ method: "POST",
65
+ headers: {
66
+ "Content-Type": "application/json",
67
+ Authorization: `Bearer ${token}`,
68
+ },
69
+ body: JSON.stringify(projectData),
70
+ });
71
+ if (!response.ok) {
72
+ const error = await response.text();
73
+ throw new Error(`Failed to register project: ${error}`);
74
+ }
75
+ const data = await response.json();
76
+ return data;
77
+ }
78
+ /**
79
+ * 現在のユーザー情報を取得
80
+ */
81
+ async getCurrentUser() {
82
+ const token = this.authManager.getToken();
83
+ if (!token) {
84
+ throw new Error("Not authenticated. Please login first.");
85
+ }
86
+ const response = await fetch(`${this.baseUrl}/auth/me`, {
87
+ method: "GET",
88
+ headers: {
89
+ Authorization: `Bearer ${token}`,
90
+ },
91
+ });
92
+ if (!response.ok) {
93
+ throw new Error("Failed to get current user");
94
+ }
95
+ const data = (await response.json());
96
+ return data.user;
97
+ }
98
+ }
package/dist/index.js ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { analyzeProject } from "./analyzer.js";
6
+ import { SeeveClient } from "./client.js";
7
+ import { AuthManager } from "./auth.js";
8
+ // MCP Server for Seevo
9
+ const server = new Server({
10
+ name: "seevo-mcp-server",
11
+ version: "1.0.0",
12
+ }, {
13
+ capabilities: {
14
+ tools: {},
15
+ },
16
+ });
17
+ // Initialize auth manager
18
+ const authManager = new AuthManager();
19
+ const seeveClient = new SeeveClient(authManager);
20
+ // Define available tools
21
+ const TOOLS = [
22
+ {
23
+ name: "seevo_register_project",
24
+ description: "現在のプロジェクトをSeevoに登録します。プロジェクトディレクトリを解析してサービス情報を自動抽出し、Seevoバックエンドに登録します。",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {
28
+ project_path: {
29
+ type: "string",
30
+ description: "プロジェクトディレクトリのパス(省略時はカレントディレクトリ)",
31
+ },
32
+ name: {
33
+ type: "string",
34
+ description: "サービス名(省略時は自動検出)",
35
+ },
36
+ },
37
+ },
38
+ },
39
+ {
40
+ name: "seevo_login",
41
+ description: "Seevoにログインして認証トークンを保存します。一度ログインすれば、以降の操作で自動的に認証されます。",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ email: {
46
+ type: "string",
47
+ description: "メールアドレス",
48
+ },
49
+ password: {
50
+ type: "string",
51
+ description: "パスワード",
52
+ },
53
+ },
54
+ required: ["email", "password"],
55
+ },
56
+ },
57
+ {
58
+ name: "seevo_logout",
59
+ description: "Seevoからログアウトして保存された認証トークンを削除します。",
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: {},
63
+ },
64
+ },
65
+ ];
66
+ // Handle list_tools request
67
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
68
+ return { tools: TOOLS };
69
+ });
70
+ // Handle call_tool request
71
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
+ const { name, arguments: args } = request.params;
73
+ try {
74
+ switch (name) {
75
+ case "seevo_register_project": {
76
+ const projectPath = args?.project_path || process.cwd();
77
+ const projectName = args?.name;
78
+ // Check authentication
79
+ if (!authManager.isAuthenticated()) {
80
+ return {
81
+ content: [
82
+ {
83
+ type: "text",
84
+ text: "エラー: Seevoにログインしていません。先に seevo_login ツールでログインしてください。",
85
+ },
86
+ ],
87
+ };
88
+ }
89
+ // Analyze project
90
+ const analysis = await analyzeProject(projectPath, projectName);
91
+ // Register to Seevo
92
+ const result = await seeveClient.registerProject(analysis);
93
+ const techStackText = result.tech_stack && result.tech_stack.length > 0
94
+ ? `\n**技術スタック**: ${result.tech_stack.join(", ")}`
95
+ : "";
96
+ const featuresText = result.features && result.features.length > 0
97
+ ? `\n**主な機能**:\n${result.features.map(f => ` - ${f}`).join("\n")}`
98
+ : "";
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: `✅ プロジェクトをSeevoに登録しました!
104
+
105
+ **サービス名**: ${result.service_name}
106
+ **説明**: ${result.description}
107
+ **ターゲット**: ${result.target}${techStackText}${featuresText}
108
+ **アプリID**: ${result.id}
109
+
110
+ Seevoダッシュボードで確認できます: http://localhost:3000/dashboard?project=${result.id}`,
111
+ },
112
+ ],
113
+ };
114
+ }
115
+ case "seevo_login": {
116
+ const email = args?.email;
117
+ const password = args?.password;
118
+ if (!email || !password) {
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: "エラー: emailとpasswordは必須です。",
124
+ },
125
+ ],
126
+ };
127
+ }
128
+ const loginResult = await seeveClient.login(email, password);
129
+ return {
130
+ content: [
131
+ {
132
+ type: "text",
133
+ text: `✅ Seevoにログインしました!
134
+
135
+ **ユーザー名**: ${loginResult.user.name}
136
+ **メール**: ${loginResult.user.email}
137
+
138
+ これで seevo_register_project ツールを使用できます。`,
139
+ },
140
+ ],
141
+ };
142
+ }
143
+ case "seevo_logout": {
144
+ authManager.clearToken();
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: "✅ Seevoからログアウトしました。",
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ default:
155
+ return {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: `エラー: 不明なツール '${name}'`,
160
+ },
161
+ ],
162
+ isError: true,
163
+ };
164
+ }
165
+ }
166
+ catch (error) {
167
+ const errorMessage = error instanceof Error ? error.message : String(error);
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text",
172
+ text: `エラー: ${errorMessage}`,
173
+ },
174
+ ],
175
+ isError: true,
176
+ };
177
+ }
178
+ });
179
+ // Start server
180
+ async function main() {
181
+ const transport = new StdioServerTransport();
182
+ await server.connect(transport);
183
+ console.error("Seevo MCP Server running on stdio");
184
+ }
185
+ main().catch((error) => {
186
+ console.error("Fatal error:", error);
187
+ process.exit(1);
188
+ });
package/dist/setup.js ADDED
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
+ import { homedir, platform } from "os";
4
+ import { join } from "path";
5
+ const SEEVO_API_URL = process.env.SEEVO_API_URL || "http://localhost:8080/api/v1";
6
+ // OS別のグローバル設定パスを取得
7
+ function getGlobalConfigPath() {
8
+ const os = platform();
9
+ switch (os) {
10
+ case "darwin": // macOS
11
+ return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
12
+ case "win32": // Windows
13
+ return join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
14
+ case "linux":
15
+ return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
16
+ default:
17
+ throw new Error(`Unsupported OS: ${os}`);
18
+ }
19
+ }
20
+ function setupMCP(token, isGlobal, projectPath) {
21
+ console.log("\n🚀 Seevo MCP Server セットアップを開始します...\n");
22
+ // トークンの検証
23
+ if (!token || !token.startsWith("seevo_mcp_")) {
24
+ console.error("❌ エラー: 有効なSeevo MCPトークンを指定してください");
25
+ console.error(" トークンは 'seevo_mcp_' で始まる必要があります\n");
26
+ process.exit(1);
27
+ }
28
+ const configPath = isGlobal
29
+ ? getGlobalConfigPath()
30
+ : join(projectPath || process.cwd(), ".mcp.json");
31
+ console.log(`📝 設定ファイル: ${configPath}`);
32
+ console.log(`🌍 スコープ: ${isGlobal ? "グローバル(全プロジェクト)" : "ローカル(このプロジェクトのみ)"}\n`);
33
+ // グローバル設定の場合はディレクトリを確認・作成
34
+ if (isGlobal) {
35
+ const configDir = join(configPath, "..");
36
+ if (!existsSync(configDir)) {
37
+ console.log("📁 設定ディレクトリを作成中...");
38
+ mkdirSync(configDir, { recursive: true });
39
+ }
40
+ }
41
+ // 既存の設定を読み込む(あれば)
42
+ let config = {};
43
+ if (existsSync(configPath)) {
44
+ try {
45
+ const existingConfig = readFileSync(configPath, "utf-8");
46
+ config = JSON.parse(existingConfig);
47
+ console.log("✓ 既存の設定ファイルを読み込みました");
48
+ }
49
+ catch (error) {
50
+ console.warn("⚠️ 既存の設定ファイルの読み込みに失敗しました。新規作成します。");
51
+ }
52
+ }
53
+ // mcpServersセクションがなければ作成
54
+ if (!config.mcpServers) {
55
+ config.mcpServers = {};
56
+ }
57
+ // Seevo MCPサーバーの設定を追加
58
+ config.mcpServers.seevo = {
59
+ command: "npx",
60
+ args: ["@zelklab/seevo-mcp-server"],
61
+ env: {
62
+ SEEVO_MCP_TOKEN: token,
63
+ SEEVO_API_URL: SEEVO_API_URL,
64
+ },
65
+ };
66
+ // 設定ファイルに書き込み
67
+ try {
68
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
69
+ console.log("✓ 設定ファイルを更新しました");
70
+ }
71
+ catch (error) {
72
+ console.error("❌ 設定ファイルの書き込みに失敗しました:", error);
73
+ process.exit(1);
74
+ }
75
+ // 完了メッセージ
76
+ console.log("\n" + "=".repeat(60));
77
+ console.log("✅ セットアップ完了!");
78
+ console.log("=".repeat(60));
79
+ console.log(`\n📍 設定ファイルの場所: ${configPath}\n`);
80
+ console.log("次のステップ:\n");
81
+ if (!isGlobal) {
82
+ console.log(` 1. プロジェクトのルートディレクトリで Claude Code を開いてください`);
83
+ console.log(` (.mcp.json が置かれているディレクトリ)`);
84
+ }
85
+ else {
86
+ console.log(` 1. Claude Codeを再起動してください`);
87
+ }
88
+ console.log(" 2. 以下のように話しかけてください:\n");
89
+ console.log(' "このプロジェクトをSeevoに登録して"\n');
90
+ if (!isGlobal) {
91
+ console.log("⚠️ 重要:");
92
+ console.log(" • .mcp.json ファイルには認証トークンが含まれています");
93
+ console.log(" • このファイルを .gitignore に追加することを推奨します:");
94
+ console.log(" echo '.mcp.json' >> .gitignore");
95
+ console.log(" • または、チームで共有する場合は環境変数を使ってください\n");
96
+ }
97
+ console.log("📚 利用可能なツール:");
98
+ console.log(" • seevo_register_project - プロジェクトを自動解析して登録");
99
+ console.log(" • seevo_login - 別アカウントでログイン");
100
+ console.log(" • seevo_logout - ログアウト\n");
101
+ console.log("💡 ヒント:Claude Codeは自動的にプロジェクトを解析し、");
102
+ console.log(" 目的、ターゲット、機能、技術スタックを理解します。\n");
103
+ }
104
+ // CLI実行
105
+ const args = process.argv.slice(2);
106
+ if (args.length === 0) {
107
+ console.error("❌ エラー: トークンを指定してください\n");
108
+ console.log("使い方:");
109
+ console.log(" npx @zelklab/seevo-mcp-server setup YOUR_TOKEN # デフォルト: ローカル");
110
+ console.log(" npx @zelklab/seevo-mcp-server setup YOUR_TOKEN --local # プロジェクトローカル(推奨)");
111
+ console.log(" npx @zelklab/seevo-mcp-server setup YOUR_TOKEN --global # グローバル設定");
112
+ console.log(" npx @zelklab/seevo-mcp-server setup YOUR_TOKEN --path /path/to/project # 別のプロジェクトに設定\n");
113
+ process.exit(1);
114
+ }
115
+ // 引数をパース
116
+ let token = "";
117
+ let isGlobal = false;
118
+ let projectPath;
119
+ // setupコマンドが含まれている場合はスキップ
120
+ const startIndex = args[0] === "setup" ? 1 : 0;
121
+ for (let i = startIndex; i < args.length; i++) {
122
+ const arg = args[i];
123
+ if (arg === "--global") {
124
+ isGlobal = true;
125
+ }
126
+ else if (arg === "--local") {
127
+ isGlobal = false;
128
+ }
129
+ else if (arg === "--path" && args[i + 1]) {
130
+ projectPath = args[i + 1];
131
+ i++; // 次の引数をスキップ
132
+ }
133
+ else if (arg === "--token" && args[i + 1]) {
134
+ token = args[i + 1];
135
+ i++; // 次の引数をスキップ
136
+ }
137
+ else if (!arg.startsWith("--") && !token) {
138
+ // フラグでなく、まだトークンが設定されていない場合
139
+ token = arg;
140
+ }
141
+ }
142
+ if (!token) {
143
+ console.error("❌ エラー: トークンが見つかりません\n");
144
+ process.exit(1);
145
+ }
146
+ // グローバルとプロジェクトパスが同時に指定された場合はエラー
147
+ if (isGlobal && projectPath) {
148
+ console.error("❌ エラー: --global と --path は同時に指定できません\n");
149
+ process.exit(1);
150
+ }
151
+ setupMCP(token, isGlobal, projectPath);
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@zelklab/seevo-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP Server for Seevo - AI-powered project registration from Claude Code",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "seevo-mcp": "./dist/setup.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc && node dist/index.js",
14
+ "setup": "node dist/setup.js",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "keywords": [
23
+ "mcp",
24
+ "seevo",
25
+ "claude-code",
26
+ "ai",
27
+ "marketing",
28
+ "automation"
29
+ ],
30
+ "author": "Your Name <your-email@example.com>",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/your-username/seevo.git",
35
+ "directory": "mcp-server"
36
+ },
37
+ "homepage": "https://github.com/your-username/seevo#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/your-username/seevo/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^0.6.0",
46
+ "node-fetch": "^3.3.2"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.0.0",
50
+ "typescript": "^5.6.0"
51
+ }
52
+ }