gitflic-cli-mcp 0.2.0 → 0.3.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 (3) hide show
  1. package/README.md +16 -1
  2. package/filter.mjs +38 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,6 +72,7 @@ claude mcp add gitflic --env GITFLIC_TOKEN=xxxxxxxx -- npx -y gitflic-cli-mcp
72
72
  |---|---|---|
73
73
  | `GITFLIC_MCP_GROUPS` | список групп через запятую/пробел | оставить только тулзы указанных групп |
74
74
  | `GITFLIC_MCP_READONLY` | `1` / `true` / `yes` / `on` | оставить только read-only тулзы (без мутаций) |
75
+ | `GITFLIC_MCP_NO_DESTRUCTIVE` | `1` / `true` / `yes` / `on` | убрать деструктивные тулзы (удаление) — см. ниже |
75
76
  | `GITFLIC_MCP_TOOLS` | список имён тулзов | явный allowlist (имеет приоритет над `GROUPS`) |
76
77
  | `GITFLIC_MCP_EXCLUDE` | список имён тулзов | убрать конкретные тулзы (применяется последним) |
77
78
 
@@ -81,7 +82,8 @@ claude mcp add gitflic --env GITFLIC_TOKEN=xxxxxxxx -- npx -y gitflic-cli-mcp
81
82
 
82
83
  1. Базовый набор: `GITFLIC_MCP_TOOLS`, если задан; иначе `GITFLIC_MCP_GROUPS`, если задан; иначе — все тулзы.
83
84
  2. `GITFLIC_MCP_READONLY` — пересечение с read-only (накладывается поверх **в т.ч. на `GITFLIC_MCP_TOOLS`** — write-тул, явно указанный в allowlist, всё равно будет отсеян; так read-only режим гарантированно не пропускает мутации).
84
- 3. `GITFLIC_MCP_EXCLUDE` — вычитается в самом конце (всегда побеждает).
85
+ 3. `GITFLIC_MCP_NO_DESTRUCTIVE` — вычитает деструктивные (удаление) тулзы (поверх базы/READONLY).
86
+ 4. `GITFLIC_MCP_EXCLUDE` — вычитается в самом конце (всегда побеждает).
85
87
 
86
88
  Значения списков — через запятую или пробел; регистр не важен ни для групп, ни для имён тулзов (имена всегда в нижнем регистре).
87
89
 
@@ -103,6 +105,16 @@ claude mcp add gitflic --env GITFLIC_TOKEN=xxxxxxxx -- npx -y gitflic-cli-mcp
103
105
 
104
106
  > ⚠️ Группа `auth` включает **мутирующие** тулзы (`gitflic_auth_login`, `_logout`, `_switch`, `_bind`, `_unbind`) — они меняют локальный keychain/конфиг, а не сервер, но это всё равно запись. Если нужны только проверки личности — добавь `GITFLIC_MCP_READONLY=1` (останутся `gitflic_auth`, `_list`, `_bindings`) или используй `GITFLIC_MCP_TOOLS` с точным набором.
105
107
 
108
+ **`GITFLIC_MCP_NO_DESTRUCTIVE=1` — выключить удаление одним флагом.**
109
+
110
+ Жёстко (на уровне регистрации, не через approval-prompt хоста) убирает **только удаляющие** тулзы — модель их вообще не видит. Это надёжнее, чем полагаться на подтверждение в хосте, которое можно прокликать или «разрешить всё агенту».
111
+
112
+ Деструктивный набор (7 — всё, что заканчивается на `_delete`):
113
+
114
+ `gitflic_issue_delete` · `gitflic_issue_comments_delete` · `gitflic_mr_discuss_delete` · `gitflic_branch_delete` · `gitflic_branch_protection_delete` · `gitflic_release_delete` · `gitflic_webhook_delete`
115
+
116
+ Остальные мутации (`*_create`, `*_edit`, `*_close`, `*_cancel`, `gitflic_mr_merge` и т.п.) **остаются** — у них сохраняется `destructiveHint: true`, так что хост по-прежнему спрашивает подтверждение. `gitflic_mr_merge` необратим, но это не удаление — он намеренно вне флага и идёт через approval.
117
+
106
118
  **Примеры:**
107
119
 
108
120
  ```bash
@@ -113,6 +125,9 @@ claude mcp add gitflic --env GITFLIC_TOKEN=xxxx \
113
125
  # Read-only агент для код-ревью (только тулзы без мутаций)
114
126
  GITFLIC_MCP_READONLY=1 npx -y gitflic-cli-mcp
115
127
 
128
+ # Может всё, кроме удаления (70 тулзов: создаёт/правит/мёрджит — но не удаляет)
129
+ GITFLIC_MCP_NO_DESTRUCTIVE=1 npx -y gitflic-cli-mcp
130
+
116
131
  # Точный набор из четырёх тулзов
117
132
  GITFLIC_MCP_TOOLS="gitflic_mr_list,gitflic_mr_view,gitflic_mr_diff,gitflic_mr_create" \
118
133
  npx -y gitflic-cli-mcp
package/filter.mjs CHANGED
@@ -30,6 +30,20 @@ export function isReadOnly(tool) {
30
30
  return Boolean(tool.annotations && tool.annotations.readOnlyHint === true);
31
31
  }
32
32
 
33
+ // "Really destructive" = irreversible deletion. This is a deliberately NARROW
34
+ // set (deletions only) used by GITFLIC_MCP_NO_DESTRUCTIVE, which removes these
35
+ // tools at registration — a hard server-side guarantee, stronger than the
36
+ // host's approval prompt (which a user can blanket-allow).
37
+ //
38
+ // Note: this is independent of the MCP `destructiveHint` annotation. Other
39
+ // mutating ops (create/edit/close/cancel and gitflic_mr_merge) keep
40
+ // destructiveHint:true and still go through the host's approval flow — they are
41
+ // NOT removed by this flag. mr_merge is irreversible but is not a deletion, so
42
+ // it is intentionally excluded here.
43
+ export function isDestructive(tool) {
44
+ return /_delete$/.test(tool.name);
45
+ }
46
+
33
47
  // Parse a comma/space-separated env value into a clean array.
34
48
  function parseList(value, { lower = false } = {}) {
35
49
  if (!value) return [];
@@ -43,11 +57,18 @@ function parseList(value, { lower = false } = {}) {
43
57
  const TRUTHY = ["1", "true", "yes", "on"];
44
58
  const FALSEY = ["0", "false", "no", "off", ""];
45
59
 
46
- // Truthy env flag: "1", "true", "yes", "on" (case-insensitive). Anything
47
- // else (including "0", "false", "", undefined) is falsey.
48
- function isTruthy(value) {
49
- if (value == null) return false;
50
- return TRUTHY.includes(String(value).trim().toLowerCase());
60
+ // Parse a boolean env flag: "1"/"true"/"yes"/"on" → true (case-insensitive);
61
+ // "0"/"false"/"no"/"off"/"" false. Anything else is treated as false but
62
+ // pushes a warning so a typo (e.g. "enabled") isn't silently ignored.
63
+ function boolFlag(raw, name, warnings) {
64
+ if (raw == null) return false;
65
+ const v = String(raw).trim().toLowerCase();
66
+ if (v !== "" && !TRUTHY.includes(v) && !FALSEY.includes(v)) {
67
+ warnings.push(
68
+ `${name}="${raw}" is not a recognized boolean (use 1/true/yes/on); treating as off.`,
69
+ );
70
+ }
71
+ return TRUTHY.includes(v);
51
72
  }
52
73
 
53
74
  // Select which tools to register based on env config.
@@ -58,7 +79,8 @@ function isTruthy(value) {
58
79
  // - GITFLIC_MCP_GROUPS (by group), if set; else
59
80
  // - all tools.
60
81
  // 2. GITFLIC_MCP_READONLY truthy → keep only read-only tools (overlay).
61
- // 3. GITFLIC_MCP_EXCLUDE → drop named tools (applied last, always wins).
82
+ // 3. GITFLIC_MCP_NO_DESTRUCTIVE truthy → drop deletion tools (overlay).
83
+ // 4. GITFLIC_MCP_EXCLUDE → drop named tools (applied last, always wins).
62
84
  //
63
85
  // Returns { selected, warnings } — warnings are human-readable strings the
64
86
  // caller prints to stderr. Unknown names/groups are warned about, not fatal.
@@ -74,16 +96,12 @@ export function selectTools(tools, env = {}) {
74
96
  const wantGroups = parseList(env.GITFLIC_MCP_GROUPS, { lower: true });
75
97
  const exclude = parseList(env.GITFLIC_MCP_EXCLUDE, { lower: true });
76
98
 
77
- const readonlyRaw = env.GITFLIC_MCP_READONLY;
78
- const readonly = isTruthy(readonlyRaw);
79
- if (readonlyRaw != null) {
80
- const v = String(readonlyRaw).trim().toLowerCase();
81
- if (v !== "" && !TRUTHY.includes(v) && !FALSEY.includes(v)) {
82
- warnings.push(
83
- `GITFLIC_MCP_READONLY="${readonlyRaw}" is not a recognized boolean (use 1/true/yes/on); treating as off.`,
84
- );
85
- }
86
- }
99
+ const readonly = boolFlag(env.GITFLIC_MCP_READONLY, "GITFLIC_MCP_READONLY", warnings);
100
+ const noDestructive = boolFlag(
101
+ env.GITFLIC_MCP_NO_DESTRUCTIVE,
102
+ "GITFLIC_MCP_NO_DESTRUCTIVE",
103
+ warnings,
104
+ );
87
105
 
88
106
  let base;
89
107
  if (wantTools.length) {
@@ -121,6 +139,10 @@ export function selectTools(tools, env = {}) {
121
139
  }
122
140
  }
123
141
 
142
+ if (noDestructive) {
143
+ base = base.filter((t) => !isDestructive(t));
144
+ }
145
+
124
146
  if (exclude.length) {
125
147
  for (const n of exclude) {
126
148
  if (!byName.has(n)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitflic-cli-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server wrapping the gitflic CLI — exposes the GitFlic REST API as typed tools for AI agents (Claude Code, Cursor, etc.)",
5
5
  "type": "module",
6
6
  "bin": {