easy-backlog-tools 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,59 @@
1
+ # easy-backlog-mcp
2
+
3
+ Claude Desktop から [easy-log](https://github.com/coco-tomoya/easy-log) の課題を操作できる MCP サーバー。
4
+
5
+ ## セットアップ
6
+
7
+ ### 1. Claude Desktop の設定を開く
8
+
9
+ `~/Library/Application Support/Claude/claude_desktop_config.json` を編集:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "easy-log": {
15
+ "command": "npx",
16
+ "args": ["-y", "easy-backlog-mcp"],
17
+ "env": {
18
+ "DATABASE_URL": "postgresql://postgres.<project-id>:<password>@aws-1-ap-northeast-1.pooler.supabase.com:6543/postgres"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ `DATABASE_URL` は Supabase プロジェクトの Transaction Pooler URL を設定する。
26
+
27
+ ### 2. Claude Desktop を再起動
28
+
29
+ 設定を反映するために Claude Desktop を再起動する。
30
+
31
+ ---
32
+
33
+ ## 使えるツール
34
+
35
+ | ツール名 | 説明 |
36
+ |---|---|
37
+ | `list_issues` | アクティブな課題を一覧表示 |
38
+ | `add_issue` | 課題を1件追加 |
39
+ | `add_issues_bulk` | 複数課題を一括追加 |
40
+ | `list_users` | メンバー一覧を表示 |
41
+
42
+ ---
43
+
44
+ ## 使い方の例
45
+
46
+ ```
47
+ 以下の課題を登録してください:
48
+ - ログイン画面の実装
49
+ - 認証APIの実装
50
+ - テストコードの作成
51
+ ```
52
+
53
+ ```
54
+ アクティブな課題を見せて
55
+ ```
56
+
57
+ ```
58
+ 山田さん担当の課題だけ見せて
59
+ ```
package/cli.js ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * easy-backlog CLI(AI agent向け設計)
4
+ */
5
+
6
+ const { listIssues, addBulk, updateBulk, deleteBulk, exec, listUsers } = require("./lib/db");
7
+
8
+ const args = process.argv.slice(2);
9
+ const cmd = args[0];
10
+
11
+ // ── フラグパース ───────────────────────────────────────
12
+ function parseFlags(args) {
13
+ const opts = { _: [] };
14
+ for (let i = 0; i < args.length; i++) {
15
+ if (args[i].startsWith("--")) {
16
+ const key = args[i].slice(2);
17
+ opts[key] = args[i + 1] !== undefined && !args[i + 1].startsWith("--") ? args[i + 1] : true;
18
+ if (opts[key] !== true) i++;
19
+ } else {
20
+ opts._.push(args[i]);
21
+ }
22
+ }
23
+ return opts;
24
+ }
25
+
26
+ // ── JSON or フラグ → 配列に正規化 ─────────────────────
27
+ function toArray(arg, fallbackOpts) {
28
+ if (!arg) return [fallbackOpts];
29
+ try {
30
+ const parsed = JSON.parse(arg);
31
+ return Array.isArray(parsed) ? parsed : [parsed];
32
+ } catch {
33
+ return [fallbackOpts];
34
+ }
35
+ }
36
+
37
+ // ── 出力 ──────────────────────────────────────────────
38
+ function output(data, json) {
39
+ if (json) {
40
+ console.log(JSON.stringify(data, null, 2));
41
+ } else if (typeof data === "string") {
42
+ console.log(data);
43
+ } else if (Array.isArray(data)) {
44
+ data.forEach(r => console.log(r.ok === false ? `✗ ${r.key ?? r.title}: ${r.error}` : `✓ ${r.key} ${r.title ?? ""}`));
45
+ } else {
46
+ console.log(JSON.stringify(data, null, 2));
47
+ }
48
+ }
49
+
50
+ function formatIssue(i) {
51
+ const indent = i.parent_key ? " └─ " : "";
52
+ const cat = i.category ? ` [${i.category}]` : "";
53
+ const who = i.assignee_name ? ` @${i.assignee_name}` : "";
54
+ const due = i.due_date ? ` 期限:${i.due_date}` : "";
55
+ return `${indent}${i.key} ${i.status} ${i.priority} | ${i.title}${cat}${who}${due}`;
56
+ }
57
+
58
+ async function main() {
59
+ const opts = parseFlags(args.slice(1));
60
+ const isJson = !!opts.json;
61
+
62
+ switch (cmd) {
63
+
64
+ // ── list ────────────────────────────────────────
65
+ case "list": {
66
+ const issues = await listIssues({
67
+ all: !!opts.all,
68
+ status: opts.status,
69
+ assignee: opts.assignee,
70
+ category: opts.category,
71
+ priority: opts.priority,
72
+ parentKey: opts.parent,
73
+ });
74
+ if (!issues.length) { console.log("該当する課題はありません"); break; }
75
+ if (isJson) { console.log(JSON.stringify(issues, null, 2)); break; }
76
+ issues.forEach(i => console.log(formatIssue(i)));
77
+ break;
78
+ }
79
+
80
+ // ── add(single/bulk)────────────────────────────
81
+ case "add": {
82
+ const jsonArg = opts._.length ? opts._[0] : null;
83
+ const items = toArray(jsonArg, {
84
+ title: opts.title, priority: opts.priority, status: opts.status,
85
+ assignee: opts.assignee, parent: opts.parent, category: opts.category,
86
+ color: opts.color, description: opts.description,
87
+ });
88
+ const results = await addBulk(items);
89
+ output(results, isJson);
90
+ break;
91
+ }
92
+
93
+ // ── update(single/bulk)─────────────────────────
94
+ case "update": {
95
+ const jsonArg = opts._.length ? opts._[0] : null;
96
+ const items = toArray(jsonArg, {
97
+ key: opts._[0], title: opts.title, priority: opts.priority,
98
+ status: opts.status, assignee: opts.assignee, category: opts.category,
99
+ color: opts.color, description: opts.description,
100
+ });
101
+ const results = await updateBulk(items);
102
+ output(results, isJson);
103
+ break;
104
+ }
105
+
106
+ // ── delete(single/bulk)─────────────────────────
107
+ case "delete": {
108
+ const jsonArg = opts._.length ? opts._[0] : null;
109
+ let keys;
110
+ try {
111
+ const parsed = JSON.parse(jsonArg);
112
+ keys = Array.isArray(parsed) ? parsed : [parsed];
113
+ } catch {
114
+ keys = opts._.length ? opts._ : [];
115
+ }
116
+ if (!keys.length) { console.error("エラー: キーを指定してください"); process.exit(1); }
117
+ const results = await deleteBulk(keys);
118
+ output(results, isJson);
119
+ break;
120
+ }
121
+
122
+ // ── exec(batch: add + update + delete 混在)──────
123
+ case "exec": {
124
+ const jsonArg = opts._.length ? opts._[0] : null;
125
+ if (!jsonArg) { console.error('エラー: JSON を指定してください\n例: exec \'{"add":[...],"update":[...],"delete":["EASY-3"]}\''); process.exit(1); }
126
+ const payload = JSON.parse(jsonArg);
127
+ const results = await exec(payload);
128
+ output(results, isJson);
129
+ break;
130
+ }
131
+
132
+ // ── users ────────────────────────────────────────
133
+ case "users": {
134
+ const users = await listUsers();
135
+ if (isJson) { console.log(JSON.stringify(users, null, 2)); break; }
136
+ users.forEach(u => console.log(`${u.name} (id:${u.id})`));
137
+ break;
138
+ }
139
+
140
+ // ── ヘルプ ───────────────────────────────────────
141
+ default:
142
+ console.log(`
143
+ easy-backlog-tools CLI
144
+
145
+ 使い方:
146
+ list [--all] [--status <状態>] [--assignee <名前>] [--category <カテゴリ>] [--priority <優先度>] [--parent <キー>] [--json]
147
+ add --title <タイトル> [--priority 高] [--category バックエンド] ...
148
+ add '[{"title":"..."},{"title":"..."}]'
149
+ update <key> --status 完了
150
+ update '[{"key":"EASY-3","status":"完了"},{"key":"EASY-4","priority":"緊急"}]'
151
+ delete EASY-3
152
+ delete '["EASY-3","EASY-4"]'
153
+ exec '{"add":[...],"update":[...],"delete":["EASY-3"]}'
154
+ users [--json]
155
+ `);
156
+ }
157
+
158
+ process.exit(0);
159
+ }
160
+
161
+ main().catch(e => {
162
+ console.error(`エラー: ${e.message}`);
163
+ process.exit(1);
164
+ });
package/index.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * easy-backlog-tools エントリーポイント
4
+ *
5
+ * --mcp → MCPサーバーモード
6
+ * 引数あり → CLIモード
7
+ * 引数なし → 環境チェック
8
+ */
9
+
10
+ const args = process.argv.slice(2);
11
+
12
+ if (args.includes("--mcp")) {
13
+ require("./server");
14
+ } else if (args.length > 0) {
15
+ require("./cli");
16
+ } else {
17
+ // 環境チェック
18
+ const ok = "✓";
19
+ const ng = "✗";
20
+
21
+ const dbUrl = process.env.DATABASE_URL;
22
+ console.log(`\neasy-backlog-tools\n`);
23
+ console.log(`${dbUrl ? ok : ng} DATABASE_URL: ${dbUrl ? "設定済み" : "未設定"}`);
24
+
25
+ if (!dbUrl) {
26
+ console.log(`\n設定方法:\n export DATABASE_URL="postgresql://..."\n`);
27
+ process.exit(1);
28
+ }
29
+
30
+ // DB接続確認
31
+ const { sql } = require("./lib/db");
32
+ sql`SELECT 1`.then(() => {
33
+ console.log(`${ok} DB接続: 成功\n`);
34
+ console.log(`使い方: easy-backlog-tools --help`);
35
+ process.exit(0);
36
+ }).catch(e => {
37
+ console.log(`${ng} DB接続: 失敗 (${e.message})\n`);
38
+ process.exit(1);
39
+ });
40
+ }
package/lib/db.js ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * easy-backlog DB操作 共通ロジック
3
+ */
4
+
5
+ const postgres = require("postgres");
6
+
7
+ const sql = postgres(process.env.DATABASE_URL, {
8
+ ssl: "require",
9
+ max: 1,
10
+ prepare: false,
11
+ idle_timeout: 60,
12
+ });
13
+
14
+ async function nextKey() {
15
+ const rows = await sql`SELECT key FROM issues ORDER BY id DESC LIMIT 1`;
16
+ if (!rows.length) return "EASY-1";
17
+ const num = parseInt(rows[0].key.split("-")[1], 10);
18
+ return `EASY-${num + 1}`;
19
+ }
20
+
21
+ async function resolveAssignee(assignee) {
22
+ if (!assignee && assignee !== "") return undefined;
23
+ if (assignee === "") return null;
24
+ const rows = await sql`SELECT id, name FROM users WHERE name LIKE ${"%" + assignee + "%"} LIMIT 1`;
25
+ if (!rows.length) {
26
+ const users = await sql`SELECT name FROM users ORDER BY name`;
27
+ throw new Error(`担当者 "${assignee}" が見つかりません。登録済み: ${users.map(u => u.name).join(", ")}`);
28
+ }
29
+ return rows[0].id;
30
+ }
31
+
32
+ async function resolveParent(parent) {
33
+ if (!parent) return null;
34
+ const rows = await sql`SELECT id, key, parent_id FROM issues WHERE key = ${parent.toUpperCase()} LIMIT 1`;
35
+ if (!rows.length) throw new Error(`親課題 "${parent}" が見つかりません`);
36
+ if (rows[0].parent_id != null) throw new Error(`"${parent}" はすでに子課題です`);
37
+ return rows[0].id;
38
+ }
39
+
40
+ // ── listIssues ────────────────────────────────────────
41
+ async function listIssues({ status, assignee, category, priority, all, parentKey } = {}) {
42
+ let query = `
43
+ SELECT i.key, i.title, i.status, i.priority, i.category, i.color, i.due_date,
44
+ u.name AS assignee_name, p.key AS parent_key
45
+ FROM issues i
46
+ LEFT JOIN users u ON u.id = i.assignee_id
47
+ LEFT JOIN issues p ON p.id = i.parent_id
48
+ WHERE 1=1
49
+ `;
50
+ const params = [];
51
+
52
+ if (status && status !== "all") {
53
+ params.push(status);
54
+ query += ` AND i.status = $${params.length}`;
55
+ } else if (!all) {
56
+ query += ` AND i.status IN ('未対応', '処理中')`;
57
+ }
58
+
59
+ if (assignee) {
60
+ params.push(`%${assignee}%`);
61
+ query += ` AND u.name LIKE $${params.length}`;
62
+ }
63
+
64
+ if (category) {
65
+ params.push(`%${category}%`);
66
+ query += ` AND i.category LIKE $${params.length}`;
67
+ }
68
+
69
+ if (priority) {
70
+ params.push(priority);
71
+ query += ` AND i.priority = $${params.length}`;
72
+ }
73
+
74
+ if (parentKey) {
75
+ params.push(parentKey.toUpperCase());
76
+ query += ` AND p.key = $${params.length}`;
77
+ }
78
+
79
+ query += ` ORDER BY COALESCE(i.parent_id, i.id) DESC, (i.parent_id IS NOT NULL), i.id ASC`;
80
+ return sql.unsafe(query, params);
81
+ }
82
+
83
+ // ── addIssue ──────────────────────────────────────────
84
+ async function addIssue({ title, priority = "中", status = "未対応", assignee, parent, description = "", category, color }) {
85
+ const assigneeId = await resolveAssignee(assignee);
86
+ const parentId = await resolveParent(parent);
87
+ const key = await nextKey();
88
+ await sql`
89
+ INSERT INTO issues (key, title, description, status, priority, assignee_id, parent_id, category, color)
90
+ VALUES (${key}, ${title}, ${description}, ${status}, ${priority},
91
+ ${assigneeId ?? null}, ${parentId}, ${category || null}, ${color || null})
92
+ `;
93
+ return { key, title, status, priority, category, color };
94
+ }
95
+
96
+ // ── addBulk ───────────────────────────────────────────
97
+ async function addBulk(issues) {
98
+ const results = [];
99
+ for (const issue of issues) {
100
+ try {
101
+ const result = await addIssue(issue);
102
+ results.push({ ok: true, ...result });
103
+ } catch (e) {
104
+ results.push({ ok: false, title: issue.title, error: e.message });
105
+ }
106
+ }
107
+ return results;
108
+ }
109
+
110
+ // ── updateIssue ───────────────────────────────────────
111
+ async function updateIssue({ key, title, status, priority, assignee, category, color, description }) {
112
+ const rows = await sql`SELECT id FROM issues WHERE key = ${key.toUpperCase()} LIMIT 1`;
113
+ if (!rows.length) throw new Error(`課題 "${key}" が見つかりません`);
114
+ const id = rows[0].id;
115
+
116
+ const fields = {};
117
+ if (title !== undefined) fields.title = title;
118
+ if (status !== undefined) fields.status = status;
119
+ if (priority !== undefined) fields.priority = priority;
120
+ if (category !== undefined) fields.category = category || null;
121
+ if (color !== undefined) fields.color = color || null;
122
+ if (description !== undefined) fields.description = description;
123
+
124
+ if (assignee !== undefined) {
125
+ fields.assignee_id = assignee === "" ? null : (await resolveAssignee(assignee));
126
+ }
127
+
128
+ if (!Object.keys(fields).length) throw new Error("変更するフィールドを指定してください");
129
+ await sql`UPDATE issues SET ${sql(fields)} WHERE id = ${id}`;
130
+ return key.toUpperCase();
131
+ }
132
+
133
+ // ── updateBulk ────────────────────────────────────────
134
+ async function updateBulk(issues) {
135
+ const results = [];
136
+ for (const issue of issues) {
137
+ try {
138
+ const key = await updateIssue(issue);
139
+ results.push({ ok: true, key });
140
+ } catch (e) {
141
+ results.push({ ok: false, key: issue.key, error: e.message });
142
+ }
143
+ }
144
+ return results;
145
+ }
146
+
147
+ // ── deleteIssue ───────────────────────────────────────
148
+ async function deleteIssue(key) {
149
+ const rows = await sql`SELECT id, title FROM issues WHERE key = ${key.toUpperCase()} LIMIT 1`;
150
+ if (!rows.length) throw new Error(`課題 "${key}" が見つかりません`);
151
+ await sql`DELETE FROM issues WHERE key = ${key.toUpperCase()}`;
152
+ return { key: key.toUpperCase(), title: rows[0].title };
153
+ }
154
+
155
+ // ── deleteBulk ────────────────────────────────────────
156
+ async function deleteBulk(keys) {
157
+ const results = [];
158
+ for (const key of keys) {
159
+ try {
160
+ const result = await deleteIssue(key);
161
+ results.push({ ok: true, ...result });
162
+ } catch (e) {
163
+ results.push({ ok: false, key, error: e.message });
164
+ }
165
+ }
166
+ return results;
167
+ }
168
+
169
+ // ── exec (batch) ──────────────────────────────────────
170
+ async function exec({ add = [], update = [], delete: del = [] }) {
171
+ const results = {};
172
+ if (add.length) results.add = await addBulk(add);
173
+ if (update.length) results.update = await updateBulk(update);
174
+ if (del.length) results.delete = await deleteBulk(del);
175
+ return results;
176
+ }
177
+
178
+ // ── listUsers ─────────────────────────────────────────
179
+ async function listUsers() {
180
+ return sql`SELECT id, name, color FROM users ORDER BY name`;
181
+ }
182
+
183
+ module.exports = { sql, listIssues, addIssue, addBulk, updateIssue, updateBulk, deleteIssue, deleteBulk, exec, listUsers };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "easy-backlog-tools",
3
+ "version": "1.0.0",
4
+ "description": "MCP server and CLI for easy-backlog issue tracker",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "easy-backlog-tools": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "server.js",
12
+ "cli.js",
13
+ "lib/"
14
+ ],
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.27.1",
17
+ "postgres": "^3.4.8",
18
+ "zod": "^3.24.0"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "keywords": ["mcp", "cli", "issue-tracker", "claude"],
24
+ "license": "MIT"
25
+ }
package/server.js ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * easy-backlog MCP サーバー
4
+ */
5
+
6
+ const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
7
+ const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
8
+ const { z } = require("zod");
9
+
10
+ if (!process.env.DATABASE_URL) {
11
+ process.stderr.write("エラー: DATABASE_URL が設定されていません\n");
12
+ process.exit(1);
13
+ }
14
+
15
+ const { listIssues, addIssue, updateIssue, deleteIssue, listUsers } = require("./lib/db");
16
+
17
+ const server = new McpServer({ name: "easy-backlog", version: "1.3.0" });
18
+
19
+ const issueSchema = {
20
+ title: z.string().optional(),
21
+ priority: z.enum(["低", "中", "高", "緊急"]).optional().default("中"),
22
+ status: z.enum(["未対応", "処理中", "完了"]).optional().default("未対応"),
23
+ assignee: z.string().optional(),
24
+ parent: z.string().optional(),
25
+ description: z.string().optional().default(""),
26
+ category: z.string().optional(),
27
+ color: z.string().optional(),
28
+ };
29
+
30
+ const ok = (text) => ({ content: [{ type: "text", text }] });
31
+ const err = (e) => ({ content: [{ type: "text", text: `エラー: ${e.message}` }] });
32
+
33
+ // ── list_issues ───────────────────────────────────────
34
+ server.tool("list_issues", "アクティブな課題を一覧表示する", {
35
+ status: z.enum(["未対応", "処理中", "完了", "all"]).optional(),
36
+ assignee: z.string().optional(),
37
+ all: z.boolean().optional(),
38
+ }, async (args) => {
39
+ try {
40
+ const issues = await listIssues(args);
41
+ if (!issues.length) return ok("該当する課題はありません");
42
+ const lines = issues.map(i => {
43
+ const indent = i.parent_key ? " └─ " : "";
44
+ const cat = i.category ? ` [${i.category}]` : "";
45
+ const who = i.assignee_name ? ` @${i.assignee_name}` : "";
46
+ const due = i.due_date ? ` 期限:${i.due_date}` : "";
47
+ return `${indent}${i.key} ${i.status} ${i.priority} | ${i.title}${cat}${who}${due}`;
48
+ });
49
+ return ok(lines.join("\n"));
50
+ } catch (e) { return err(e); }
51
+ });
52
+
53
+ // ── add_issue ─────────────────────────────────────────
54
+ server.tool("add_issue", "課題を1件追加する", {
55
+ title: z.string(),
56
+ ...issueSchema,
57
+ }, async (args) => {
58
+ try {
59
+ const { key, title, status, priority, category, color } = await addIssue(args);
60
+ const lines = [
61
+ `✓ 課題を追加しました`,
62
+ ` キー : ${key}`,
63
+ ` タイトル: ${title}`,
64
+ ` 状態 : ${status}`,
65
+ ` 優先度 : ${priority}`,
66
+ category ? ` カテゴリ: ${category}` : null,
67
+ color ? ` カラー : ${color}` : null,
68
+ ].filter(Boolean);
69
+ return ok(lines.join("\n"));
70
+ } catch (e) { return err(e); }
71
+ });
72
+
73
+ // ── add_issues_bulk ───────────────────────────────────
74
+ server.tool("add_issues_bulk", "複数の課題を一括追加する", {
75
+ issues: z.array(z.object({ title: z.string(), ...issueSchema })),
76
+ }, async ({ issues }) => {
77
+ const results = [];
78
+ for (const issue of issues) {
79
+ try {
80
+ const { key, title, category } = await addIssue(issue);
81
+ results.push(`✓ ${key} : ${title}${category ? ` [${category}]` : ""}`);
82
+ } catch (e) {
83
+ results.push(`✗ ${issue.title}: ${e.message}`);
84
+ }
85
+ }
86
+ return ok(results.join("\n"));
87
+ });
88
+
89
+ // ── update_issue ──────────────────────────────────────
90
+ server.tool("update_issue", "課題を更新する", {
91
+ key: z.string(),
92
+ ...issueSchema,
93
+ }, async (args) => {
94
+ try {
95
+ const key = await updateIssue(args);
96
+ return ok(`✓ ${key} を更新しました`);
97
+ } catch (e) { return err(e); }
98
+ });
99
+
100
+ // ── update_issues_bulk ────────────────────────────────
101
+ server.tool("update_issues_bulk", "複数の課題を一括更新する", {
102
+ issues: z.array(z.object({ key: z.string(), ...issueSchema })),
103
+ }, async ({ issues }) => {
104
+ const results = [];
105
+ for (const issue of issues) {
106
+ try {
107
+ const key = await updateIssue(issue);
108
+ results.push(`✓ ${key} を更新しました`);
109
+ } catch (e) {
110
+ results.push(`✗ ${issue.key}: ${e.message}`);
111
+ }
112
+ }
113
+ return ok(results.join("\n"));
114
+ });
115
+
116
+ // ── delete_issue ──────────────────────────────────────
117
+ server.tool("delete_issue", "課題を削除する", {
118
+ key: z.string(),
119
+ }, async ({ key }) => {
120
+ try {
121
+ const title = await deleteIssue(key);
122
+ return ok(`✓ ${key.toUpperCase()} "${title}" を削除しました`);
123
+ } catch (e) { return err(e); }
124
+ });
125
+
126
+ // ── delete_issues_bulk ────────────────────────────────
127
+ server.tool("delete_issues_bulk", "複数の課題を一括削除する", {
128
+ keys: z.array(z.string()),
129
+ }, async ({ keys }) => {
130
+ const results = [];
131
+ for (const key of keys) {
132
+ try {
133
+ const title = await deleteIssue(key);
134
+ results.push(`✓ ${key.toUpperCase()} "${title}" を削除しました`);
135
+ } catch (e) {
136
+ results.push(`✗ ${key}: ${e.message}`);
137
+ }
138
+ }
139
+ return ok(results.join("\n"));
140
+ });
141
+
142
+ // ── list_users ────────────────────────────────────────
143
+ server.tool("list_users", "登録済みメンバーを一覧表示する", {}, async () => {
144
+ try {
145
+ const users = await listUsers();
146
+ if (!users.length) return ok("メンバーが登録されていません");
147
+ return ok(users.map(u => `${u.name} (id:${u.id}, color:${u.color})`).join("\n"));
148
+ } catch (e) { return err(e); }
149
+ });
150
+
151
+ // ── 起動 ──────────────────────────────────────────────
152
+ async function main() {
153
+ const transport = new StdioServerTransport();
154
+ await server.connect(transport);
155
+ process.stderr.write("easy-backlog MCP サーバー起動\n");
156
+ }
157
+
158
+ main().catch(err => {
159
+ process.stderr.write(`エラー: ${err.message}\n`);
160
+ process.exit(1);
161
+ });