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.
- package/README.md +16 -1
- package/filter.mjs +38 -16
- 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. `
|
|
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
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
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
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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