@aplz/mcp-server 1.0.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,86 @@
1
+ # @aplz/mcp-server
2
+
3
+ Claude Desktop / Claude Code / Cursor などの AIツールから [aplz.dev](https://aplz.dev) にWebアプリを公開できるMCPサーバーです。
4
+
5
+ ## セットアップ
6
+
7
+ ### 1. APIトークンを取得
8
+
9
+ 1. [aplz.dev](https://aplz.dev) にログイン
10
+ 2. 右上のアバター → **APIトークン設定** に移動
11
+ 3. トークン名を入力して「トークンを生成」をクリック
12
+ 4. 生成されたトークン(`aplz_...`)をコピー(**再表示不可**)
13
+
14
+ ### 2. Claude Desktop に設定
15
+
16
+ `~/Library/Application Support/Claude/claude_desktop_config.json` を編集:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "aplz": {
22
+ "command": "npx",
23
+ "args": ["-y", "@aplz/mcp-server"],
24
+ "env": {
25
+ "APLZ_API_TOKEN": "aplz_ここにトークンを貼り付け"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ Claude Desktop を再起動するとツールが使えるようになります。
33
+
34
+ ### ローカルビルドして使う場合
35
+
36
+ ```bash
37
+ cd mcp-server
38
+ npm install
39
+ npm run build
40
+ ```
41
+
42
+ claude_desktop_config.json:
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "aplz": {
47
+ "command": "node",
48
+ "args": ["/path/to/aplz/mcp-server/dist/index.js"],
49
+ "env": {
50
+ "APLZ_API_TOKEN": "aplz_xxxxxxxxxxxx"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## 使い方
58
+
59
+ Claude に話しかけるだけ:
60
+
61
+ ```
62
+ このHTMLファイルをaplzに公開して: /path/to/app.html
63
+ ```
64
+
65
+ ```
66
+ aplzに公開した自分のアプリの一覧を見せて
67
+ ```
68
+
69
+ ```
70
+ fish-tetris のフィードバックを教えて
71
+ ```
72
+
73
+ ## 利用可能なツール
74
+
75
+ | ツール | 説明 |
76
+ |--------|------|
77
+ | `publish_app` | HTMLまたはZIPファイルをaplzに公開 |
78
+ | `list_apps` | 自分が公開したアプリの一覧を取得 |
79
+ | `get_feedback` | 特定アプリの評価・コメント・リアクションを取得 |
80
+
81
+ ## 環境変数
82
+
83
+ | 変数名 | 説明 |
84
+ |--------|------|
85
+ | `APLZ_API_TOKEN` | APIトークン(必須) |
86
+ | `APLZ_API_BASE` | APIのベースURL(デフォルト: `https://aplz.dev`) |
package/dist/index.js ADDED
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
38
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
39
+ const zod_1 = require("zod");
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const server = new mcp_js_1.McpServer({
43
+ name: "aplz",
44
+ version: "1.0.0",
45
+ });
46
+ const API_BASE = process.env.APLZ_API_BASE ?? "https://aplz.dev";
47
+ function getToken() {
48
+ const token = process.env.APLZ_API_TOKEN;
49
+ if (!token)
50
+ throw new Error("APLZ_API_TOKEN environment variable is required");
51
+ return token;
52
+ }
53
+ // ─── Tool: publish_app ────────────────────────────────────────────────────────
54
+ server.tool("publish_app", "aplzにWebアプリを公開する。HTMLファイルまたはZIPファイルのパスを指定。", {
55
+ title: zod_1.z.string().describe("アプリのタイトル"),
56
+ file_path: zod_1.z.string().describe("公開するHTMLファイルまたはZIPファイルのパス"),
57
+ description: zod_1.z.string().optional().describe("アプリの説明(省略可)"),
58
+ community_slug: zod_1.z.string().optional().describe("公開先コミュニティのslug(省略時はオープン公開)"),
59
+ is_public: zod_1.z.boolean().default(true).describe("オープンに公開するか(デフォルト: true)"),
60
+ }, async ({ title, file_path, description, community_slug, is_public }) => {
61
+ const token = getToken();
62
+ const resolvedPath = path.resolve(file_path);
63
+ if (!fs.existsSync(resolvedPath)) {
64
+ return {
65
+ content: [{ type: "text", text: `エラー: ファイルが見つかりません: ${resolvedPath}` }],
66
+ };
67
+ }
68
+ const stat = fs.statSync(resolvedPath);
69
+ if (stat.isDirectory()) {
70
+ return {
71
+ content: [{ type: "text", text: "エラー: ディレクトリは現在未サポートです。index.htmlまたはZIPファイルを指定してください。" }],
72
+ };
73
+ }
74
+ const ext = path.extname(resolvedPath).toLowerCase();
75
+ if (ext !== ".html" && ext !== ".zip") {
76
+ return {
77
+ content: [{ type: "text", text: "エラー: .htmlまたは.zipファイルのみ対応しています。" }],
78
+ };
79
+ }
80
+ const fileBuffer = fs.readFileSync(resolvedPath);
81
+ const mimeType = ext === ".zip" ? "application/zip" : "text/html";
82
+ const blob = new Blob([fileBuffer], { type: mimeType });
83
+ const formData = new FormData();
84
+ formData.append("file", blob, path.basename(resolvedPath));
85
+ formData.append("name", title);
86
+ if (description)
87
+ formData.append("description", description);
88
+ if (community_slug)
89
+ formData.append("community_slug", community_slug);
90
+ formData.append("is_public", String(is_public));
91
+ let response;
92
+ try {
93
+ response = await fetch(`${API_BASE}/api/publish`, {
94
+ method: "POST",
95
+ headers: { Authorization: `Bearer ${token}` },
96
+ body: formData,
97
+ });
98
+ }
99
+ catch (e) {
100
+ return {
101
+ content: [{ type: "text", text: `ネットワークエラー: ${e instanceof Error ? e.message : String(e)}` }],
102
+ };
103
+ }
104
+ if (!response.ok) {
105
+ const errorText = await response.text();
106
+ return {
107
+ content: [{ type: "text", text: `公開失敗 (${response.status}): ${errorText}` }],
108
+ };
109
+ }
110
+ const result = await response.json();
111
+ if (!result.success) {
112
+ return { content: [{ type: "text", text: `公開失敗: ${result.error}` }] };
113
+ }
114
+ return {
115
+ content: [{
116
+ type: "text",
117
+ text: [
118
+ "公開成功!",
119
+ `アプリURL (直接起動): ${result.app_url}`,
120
+ `フィードバックページ: ${result.platform_url}`,
121
+ `slug: ${result.slug}`,
122
+ ].join("\n"),
123
+ }],
124
+ };
125
+ });
126
+ // ─── Tool: list_apps ──────────────────────────────────────────────────────────
127
+ server.tool("list_apps", "aplzに公開した自分のアプリ一覧を取得する", {
128
+ limit: zod_1.z.number().int().min(1).max(50).default(10).describe("取得件数(最大50)"),
129
+ }, async ({ limit }) => {
130
+ const token = getToken();
131
+ let response;
132
+ try {
133
+ response = await fetch(`${API_BASE}/api/apps/mine?limit=${limit}`, {
134
+ headers: { Authorization: `Bearer ${token}` },
135
+ });
136
+ }
137
+ catch (e) {
138
+ return {
139
+ content: [{ type: "text", text: `ネットワークエラー: ${e instanceof Error ? e.message : String(e)}` }],
140
+ };
141
+ }
142
+ if (!response.ok) {
143
+ return { content: [{ type: "text", text: "アプリ一覧の取得に失敗しました" }] };
144
+ }
145
+ const apps = await response.json();
146
+ if (!apps.length) {
147
+ return { content: [{ type: "text", text: "まだアプリを公開していません" }] };
148
+ }
149
+ const lines = apps.map((app, i) => {
150
+ const rating = app.avg_rating ? `★${app.avg_rating}` : "評価なし";
151
+ return `${i + 1}. ${app.name} (${rating}, ${app.comment_count}コメント) - ${API_BASE}/apps/${app.slug}`;
152
+ });
153
+ return {
154
+ content: [{ type: "text", text: `あなたのアプリ一覧:\n${lines.join("\n")}` }],
155
+ };
156
+ });
157
+ // ─── Tool: get_feedback ───────────────────────────────────────────────────────
158
+ server.tool("get_feedback", "aplzに公開したアプリのフィードバック(評価・コメント・リアクション)を取得する", {
159
+ app_slug: zod_1.z.string().describe("アプリのslug(URLの /apps/[slug] 部分)"),
160
+ }, async ({ app_slug }) => {
161
+ const token = getToken();
162
+ let response;
163
+ try {
164
+ response = await fetch(`${API_BASE}/api/apps/${app_slug}/feedback`, {
165
+ headers: { Authorization: `Bearer ${token}` },
166
+ });
167
+ }
168
+ catch (e) {
169
+ return {
170
+ content: [{ type: "text", text: `ネットワークエラー: ${e instanceof Error ? e.message : String(e)}` }],
171
+ };
172
+ }
173
+ if (response.status === 404) {
174
+ return { content: [{ type: "text", text: `アプリ "${app_slug}" が見つかりません` }] };
175
+ }
176
+ if (!response.ok) {
177
+ return { content: [{ type: "text", text: "フィードバックの取得に失敗しました" }] };
178
+ }
179
+ const data = await response.json();
180
+ const lines = [
181
+ `「${data.title}」のフィードバック:`,
182
+ `評価: ${data.avg_rating ? `★${data.avg_rating}` : "なし"}(${data.rating_count}件)`,
183
+ `リアクション: いいね ${data.reactions.like} / 使いたい ${data.reactions.want} / すごい ${data.reactions.amazing} / 改善点あり ${data.reactions.feedback}`,
184
+ "",
185
+ "コメント:",
186
+ ];
187
+ if (data.comments.length === 0) {
188
+ lines.push("(まだコメントはありません)");
189
+ }
190
+ else {
191
+ for (const c of data.comments) {
192
+ lines.push(`- "${c.content}" (${c.time_ago})`);
193
+ }
194
+ }
195
+ return { content: [{ type: "text", text: lines.join("\n") }] };
196
+ });
197
+ // ─── Setup mode ───────────────────────────────────────────────────────────────
198
+ function getConfigPath() {
199
+ const platform = process.platform;
200
+ if (platform === "darwin") {
201
+ return path.join(process.env.HOME ?? "~", "Library", "Application Support", "Claude", "claude_desktop_config.json");
202
+ }
203
+ else if (platform === "win32") {
204
+ return path.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
205
+ }
206
+ else {
207
+ return path.join(process.env.HOME ?? "~", ".config", "Claude", "claude_desktop_config.json");
208
+ }
209
+ }
210
+ async function runSetup(token) {
211
+ const configPath = getConfigPath();
212
+ const configDir = path.dirname(configPath);
213
+ // Ensure directory exists
214
+ if (!fs.existsSync(configDir)) {
215
+ fs.mkdirSync(configDir, { recursive: true });
216
+ }
217
+ // Read existing config or start fresh
218
+ let config = {};
219
+ if (fs.existsSync(configPath)) {
220
+ try {
221
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
222
+ }
223
+ catch {
224
+ // Corrupted JSON — start fresh
225
+ }
226
+ }
227
+ // Merge aplz entry
228
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
229
+ config.mcpServers = {};
230
+ }
231
+ config.mcpServers.aplz = {
232
+ command: "npx",
233
+ args: ["@aplz/mcp-server"],
234
+ env: { APLZ_API_TOKEN: token },
235
+ };
236
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
237
+ console.log("セットアップ完了!Claude Desktopを再起動してください。");
238
+ console.log("以降はClaudeに「このアプリをaplzに公開して」と言うだけで公開できます。");
239
+ // Try to restart Claude Desktop (Mac only, best-effort)
240
+ if (process.platform === "darwin") {
241
+ try {
242
+ const { execSync } = await import("child_process");
243
+ try {
244
+ execSync("killall Claude 2>/dev/null", { stdio: "ignore" });
245
+ }
246
+ catch { /* not running */ }
247
+ await new Promise((r) => setTimeout(r, 1000));
248
+ try {
249
+ execSync("open -a Claude 2>/dev/null", { stdio: "ignore" });
250
+ }
251
+ catch { /* not installed */ }
252
+ }
253
+ catch { /* ignore */ }
254
+ }
255
+ }
256
+ // ─── Start ────────────────────────────────────────────────────────────────────
257
+ async function main() {
258
+ const setupIdx = process.argv.indexOf("--setup");
259
+ if (setupIdx !== -1) {
260
+ const token = process.argv[setupIdx + 1];
261
+ if (!token || !token.startsWith("aplz_")) {
262
+ console.error("使い方: npx @aplz/mcp-server --setup aplz_xxxxxxxxxxxxxxxx");
263
+ process.exit(1);
264
+ }
265
+ await runSetup(token);
266
+ return;
267
+ }
268
+ const transport = new stdio_js_1.StdioServerTransport();
269
+ await server.connect(transport);
270
+ }
271
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@aplz/mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "Aplz MCP Server - AIからWebアプリを公開",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "aplz-mcp": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "tsc --watch"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.12.0",
16
+ "zod": "^3.25.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "typescript": "^5.7.0"
21
+ }
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+
8
+ const server = new McpServer({
9
+ name: "aplz",
10
+ version: "1.0.0",
11
+ });
12
+
13
+ const API_BASE = process.env.APLZ_API_BASE ?? "https://aplz.dev";
14
+
15
+ function getToken(): string {
16
+ const token = process.env.APLZ_API_TOKEN;
17
+ if (!token) throw new Error("APLZ_API_TOKEN environment variable is required");
18
+ return token;
19
+ }
20
+
21
+ // ─── Tool: publish_app ────────────────────────────────────────────────────────
22
+
23
+ server.tool(
24
+ "publish_app",
25
+ "aplzにWebアプリを公開する。HTMLファイルまたはZIPファイルのパスを指定。",
26
+ {
27
+ title: z.string().describe("アプリのタイトル"),
28
+ file_path: z.string().describe("公開するHTMLファイルまたはZIPファイルのパス"),
29
+ description: z.string().optional().describe("アプリの説明(省略可)"),
30
+ community_slug: z.string().optional().describe("公開先コミュニティのslug(省略時はオープン公開)"),
31
+ is_public: z.boolean().default(true).describe("オープンに公開するか(デフォルト: true)"),
32
+ },
33
+ async ({ title, file_path, description, community_slug, is_public }) => {
34
+ const token = getToken();
35
+ const resolvedPath = path.resolve(file_path);
36
+
37
+ if (!fs.existsSync(resolvedPath)) {
38
+ return {
39
+ content: [{ type: "text", text: `エラー: ファイルが見つかりません: ${resolvedPath}` }],
40
+ };
41
+ }
42
+
43
+ const stat = fs.statSync(resolvedPath);
44
+ if (stat.isDirectory()) {
45
+ return {
46
+ content: [{ type: "text", text: "エラー: ディレクトリは現在未サポートです。index.htmlまたはZIPファイルを指定してください。" }],
47
+ };
48
+ }
49
+
50
+ const ext = path.extname(resolvedPath).toLowerCase();
51
+ if (ext !== ".html" && ext !== ".zip") {
52
+ return {
53
+ content: [{ type: "text", text: "エラー: .htmlまたは.zipファイルのみ対応しています。" }],
54
+ };
55
+ }
56
+
57
+ const fileBuffer = fs.readFileSync(resolvedPath);
58
+ const mimeType = ext === ".zip" ? "application/zip" : "text/html";
59
+ const blob = new Blob([fileBuffer], { type: mimeType });
60
+
61
+ const formData = new FormData();
62
+ formData.append("file", blob, path.basename(resolvedPath));
63
+ formData.append("name", title);
64
+ if (description) formData.append("description", description);
65
+ if (community_slug) formData.append("community_slug", community_slug);
66
+ formData.append("is_public", String(is_public));
67
+
68
+ let response: Response;
69
+ try {
70
+ response = await fetch(`${API_BASE}/api/publish`, {
71
+ method: "POST",
72
+ headers: { Authorization: `Bearer ${token}` },
73
+ body: formData,
74
+ });
75
+ } catch (e) {
76
+ return {
77
+ content: [{ type: "text", text: `ネットワークエラー: ${e instanceof Error ? e.message : String(e)}` }],
78
+ };
79
+ }
80
+
81
+ if (!response.ok) {
82
+ const errorText = await response.text();
83
+ return {
84
+ content: [{ type: "text", text: `公開失敗 (${response.status}): ${errorText}` }],
85
+ };
86
+ }
87
+
88
+ const result = await response.json() as { success: boolean; slug?: string; app_url?: string; platform_url?: string; error?: string };
89
+ if (!result.success) {
90
+ return { content: [{ type: "text", text: `公開失敗: ${result.error}` }] };
91
+ }
92
+
93
+ return {
94
+ content: [{
95
+ type: "text",
96
+ text: [
97
+ "公開成功!",
98
+ `アプリURL (直接起動): ${result.app_url}`,
99
+ `フィードバックページ: ${result.platform_url}`,
100
+ `slug: ${result.slug}`,
101
+ ].join("\n"),
102
+ }],
103
+ };
104
+ }
105
+ );
106
+
107
+ // ─── Tool: list_apps ──────────────────────────────────────────────────────────
108
+
109
+ server.tool(
110
+ "list_apps",
111
+ "aplzに公開した自分のアプリ一覧を取得する",
112
+ {
113
+ limit: z.number().int().min(1).max(50).default(10).describe("取得件数(最大50)"),
114
+ },
115
+ async ({ limit }) => {
116
+ const token = getToken();
117
+
118
+ let response: Response;
119
+ try {
120
+ response = await fetch(`${API_BASE}/api/apps/mine?limit=${limit}`, {
121
+ headers: { Authorization: `Bearer ${token}` },
122
+ });
123
+ } catch (e) {
124
+ return {
125
+ content: [{ type: "text", text: `ネットワークエラー: ${e instanceof Error ? e.message : String(e)}` }],
126
+ };
127
+ }
128
+
129
+ if (!response.ok) {
130
+ return { content: [{ type: "text", text: "アプリ一覧の取得に失敗しました" }] };
131
+ }
132
+
133
+ const apps = await response.json() as Array<{ name: string; slug: string; avg_rating: string | null; comment_count: number }>;
134
+
135
+ if (!apps.length) {
136
+ return { content: [{ type: "text", text: "まだアプリを公開していません" }] };
137
+ }
138
+
139
+ const lines = apps.map((app, i) => {
140
+ const rating = app.avg_rating ? `★${app.avg_rating}` : "評価なし";
141
+ return `${i + 1}. ${app.name} (${rating}, ${app.comment_count}コメント) - ${API_BASE}/apps/${app.slug}`;
142
+ });
143
+
144
+ return {
145
+ content: [{ type: "text", text: `あなたのアプリ一覧:\n${lines.join("\n")}` }],
146
+ };
147
+ }
148
+ );
149
+
150
+ // ─── Tool: get_feedback ───────────────────────────────────────────────────────
151
+
152
+ server.tool(
153
+ "get_feedback",
154
+ "aplzに公開したアプリのフィードバック(評価・コメント・リアクション)を取得する",
155
+ {
156
+ app_slug: z.string().describe("アプリのslug(URLの /apps/[slug] 部分)"),
157
+ },
158
+ async ({ app_slug }) => {
159
+ const token = getToken();
160
+
161
+ let response: Response;
162
+ try {
163
+ response = await fetch(`${API_BASE}/api/apps/${app_slug}/feedback`, {
164
+ headers: { Authorization: `Bearer ${token}` },
165
+ });
166
+ } catch (e) {
167
+ return {
168
+ content: [{ type: "text", text: `ネットワークエラー: ${e instanceof Error ? e.message : String(e)}` }],
169
+ };
170
+ }
171
+
172
+ if (response.status === 404) {
173
+ return { content: [{ type: "text", text: `アプリ "${app_slug}" が見つかりません` }] };
174
+ }
175
+ if (!response.ok) {
176
+ return { content: [{ type: "text", text: "フィードバックの取得に失敗しました" }] };
177
+ }
178
+
179
+ const data = await response.json() as {
180
+ title: string;
181
+ avg_rating: string | null;
182
+ rating_count: number;
183
+ reactions: { like: number; want: number; amazing: number; feedback: number };
184
+ comments: Array<{ content: string; time_ago: string }>;
185
+ };
186
+
187
+ const lines = [
188
+ `「${data.title}」のフィードバック:`,
189
+ `評価: ${data.avg_rating ? `★${data.avg_rating}` : "なし"}(${data.rating_count}件)`,
190
+ `リアクション: いいね ${data.reactions.like} / 使いたい ${data.reactions.want} / すごい ${data.reactions.amazing} / 改善点あり ${data.reactions.feedback}`,
191
+ "",
192
+ "コメント:",
193
+ ];
194
+
195
+ if (data.comments.length === 0) {
196
+ lines.push("(まだコメントはありません)");
197
+ } else {
198
+ for (const c of data.comments) {
199
+ lines.push(`- "${c.content}" (${c.time_ago})`);
200
+ }
201
+ }
202
+
203
+ return { content: [{ type: "text", text: lines.join("\n") }] };
204
+ }
205
+ );
206
+
207
+ // ─── Setup mode ───────────────────────────────────────────────────────────────
208
+
209
+ function getConfigPath(): string {
210
+ const platform = process.platform;
211
+ if (platform === "darwin") {
212
+ return path.join(process.env.HOME ?? "~", "Library", "Application Support", "Claude", "claude_desktop_config.json");
213
+ } else if (platform === "win32") {
214
+ return path.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
215
+ } else {
216
+ return path.join(process.env.HOME ?? "~", ".config", "Claude", "claude_desktop_config.json");
217
+ }
218
+ }
219
+
220
+ async function runSetup(token: string): Promise<void> {
221
+ const configPath = getConfigPath();
222
+ const configDir = path.dirname(configPath);
223
+
224
+ // Ensure directory exists
225
+ if (!fs.existsSync(configDir)) {
226
+ fs.mkdirSync(configDir, { recursive: true });
227
+ }
228
+
229
+ // Read existing config or start fresh
230
+ let config: Record<string, unknown> = {};
231
+ if (fs.existsSync(configPath)) {
232
+ try {
233
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
234
+ } catch {
235
+ // Corrupted JSON — start fresh
236
+ }
237
+ }
238
+
239
+ // Merge aplz entry
240
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
241
+ config.mcpServers = {};
242
+ }
243
+ (config.mcpServers as Record<string, unknown>).aplz = {
244
+ command: "npx",
245
+ args: ["@aplz/mcp-server"],
246
+ env: { APLZ_API_TOKEN: token },
247
+ };
248
+
249
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
250
+
251
+ console.log("セットアップ完了!Claude Desktopを再起動してください。");
252
+ console.log("以降はClaudeに「このアプリをaplzに公開して」と言うだけで公開できます。");
253
+
254
+ // Try to restart Claude Desktop (Mac only, best-effort)
255
+ if (process.platform === "darwin") {
256
+ try {
257
+ const { execSync } = await import("child_process");
258
+ try { execSync("killall Claude 2>/dev/null", { stdio: "ignore" }); } catch { /* not running */ }
259
+ await new Promise((r) => setTimeout(r, 1000));
260
+ try { execSync("open -a Claude 2>/dev/null", { stdio: "ignore" }); } catch { /* not installed */ }
261
+ } catch { /* ignore */ }
262
+ }
263
+ }
264
+
265
+ // ─── Start ────────────────────────────────────────────────────────────────────
266
+
267
+ async function main() {
268
+ const setupIdx = process.argv.indexOf("--setup");
269
+ if (setupIdx !== -1) {
270
+ const token = process.argv[setupIdx + 1];
271
+ if (!token || !token.startsWith("aplz_")) {
272
+ console.error("使い方: npx @aplz/mcp-server --setup aplz_xxxxxxxxxxxxxxxx");
273
+ process.exit(1);
274
+ }
275
+ await runSetup(token);
276
+ return;
277
+ }
278
+
279
+ const transport = new StdioServerTransport();
280
+ await server.connect(transport);
281
+ }
282
+
283
+ main().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src/**/*"]
13
+ }