@greenflags/mcp 0.2.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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/api.d.ts +1 -0
- package/dist/api.js +46 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +100 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +37 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +24 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -0
- package/dist/resolvers.d.ts +22 -0
- package/dist/resolvers.js +92 -0
- package/dist/result.d.ts +10 -0
- package/dist/result.js +37 -0
- package/dist/tools/archiveFlag.d.ts +31 -0
- package/dist/tools/archiveFlag.js +45 -0
- package/dist/tools/createFlag.d.ts +41 -0
- package/dist/tools/createFlag.js +57 -0
- package/dist/tools/getFlag.d.ts +32 -0
- package/dist/tools/getFlag.js +48 -0
- package/dist/tools/getFlagValues.d.ts +35 -0
- package/dist/tools/getFlagValues.js +77 -0
- package/dist/tools/listEnvironments.d.ts +23 -0
- package/dist/tools/listEnvironments.js +41 -0
- package/dist/tools/listFlags.d.ts +36 -0
- package/dist/tools/listFlags.js +94 -0
- package/dist/tools/listProjects.d.ts +14 -0
- package/dist/tools/listProjects.js +34 -0
- package/dist/tools/setFlagValue.d.ts +61 -0
- package/dist/tools/setFlagValue.js +123 -0
- package/dist/tools/updateFlag.d.ts +40 -0
- package/dist/tools/updateFlag.js +60 -0
- package/package.json +38 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type McpToolResult } from "../result.js";
|
|
2
|
+
export declare const toolDefinition: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
annotations: {
|
|
6
|
+
destructiveHint: boolean;
|
|
7
|
+
idempotentHint: boolean;
|
|
8
|
+
};
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object";
|
|
11
|
+
properties: {
|
|
12
|
+
project: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
project_id: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
flag: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
flag_id: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
environment: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
environment_id: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
type: {
|
|
37
|
+
type: string;
|
|
38
|
+
enum: string[];
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
value_boolean: {
|
|
42
|
+
type: string;
|
|
43
|
+
};
|
|
44
|
+
value_string: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
value_number: {
|
|
48
|
+
type: string;
|
|
49
|
+
};
|
|
50
|
+
value_json: {
|
|
51
|
+
type: string;
|
|
52
|
+
};
|
|
53
|
+
confirm: {
|
|
54
|
+
type: string;
|
|
55
|
+
description: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
required: never[];
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export declare function handler(args: Record<string, unknown>): Promise<McpToolResult>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { callApi } from "../api.js";
|
|
2
|
+
import { requireProjectId } from "../config.js";
|
|
3
|
+
import { ApiError, SensitiveConfirmRequiredError } from "../errors.js";
|
|
4
|
+
import { okResult } from "../result.js";
|
|
5
|
+
import { invalidateFlagCache, resolveEnvironment, resolveFlag, } from "../resolvers.js";
|
|
6
|
+
export const toolDefinition = {
|
|
7
|
+
name: "set_flag_value",
|
|
8
|
+
description: "Set a flag's value in one environment. Supports boolean/string/number/json (replaces the old toggle_flag_value).\n" +
|
|
9
|
+
"Use when: enabling/disabling a flag or setting its typed value in an env.\n" +
|
|
10
|
+
"Prerequisites: a project, an existing flag, and an environment. Call list_environments first to see which envs are sensitive.\n" +
|
|
11
|
+
"Side effects: DESTRUCTIVE (can change production behavior). Writes to sensitive envs REQUIRE confirm:true or the call is refused. Idempotent — same value yields changed:false.\n" +
|
|
12
|
+
'Example: set_flag_value(flag: "checkout-v2", environment: "staging", value_boolean: true).',
|
|
13
|
+
annotations: {
|
|
14
|
+
destructiveHint: true,
|
|
15
|
+
idempotentHint: true,
|
|
16
|
+
},
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
project: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Project id or slug. Optional if GREENFLAGS_PROJECT_ID is set.",
|
|
23
|
+
},
|
|
24
|
+
project_id: { type: "string", description: "Legacy alias for `project`." },
|
|
25
|
+
flag: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Flag key (preferred) or flag id.",
|
|
28
|
+
},
|
|
29
|
+
flag_id: { type: "string", description: "Legacy alias for `flag`." },
|
|
30
|
+
environment: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Environment slug (preferred) or environment id.",
|
|
33
|
+
},
|
|
34
|
+
environment_id: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Legacy alias for `environment`.",
|
|
37
|
+
},
|
|
38
|
+
type: {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: ["boolean", "string", "number", "json"],
|
|
41
|
+
description: "Override flag value type. Defaults to the flag's declared type.",
|
|
42
|
+
},
|
|
43
|
+
value_boolean: { type: "boolean" },
|
|
44
|
+
value_string: { type: "string" },
|
|
45
|
+
value_number: { type: "number" },
|
|
46
|
+
value_json: { type: "object" },
|
|
47
|
+
confirm: {
|
|
48
|
+
type: "boolean",
|
|
49
|
+
description: "Required when the target environment is sensitive (e.g. prod). Set true to acknowledge the write.",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: [],
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
function pickTypedValue(type, args) {
|
|
56
|
+
switch (type) {
|
|
57
|
+
case "boolean":
|
|
58
|
+
if (typeof args["value_boolean"] !== "boolean") {
|
|
59
|
+
throw new ApiError("VALIDATION_FAILED", "Flag type is boolean — pass `value_boolean: true|false`.", 422);
|
|
60
|
+
}
|
|
61
|
+
return { key: "valueBoolean", value: args["value_boolean"] };
|
|
62
|
+
case "string":
|
|
63
|
+
if (typeof args["value_string"] !== "string") {
|
|
64
|
+
throw new ApiError("VALIDATION_FAILED", "Flag type is string — pass `value_string`.", 422);
|
|
65
|
+
}
|
|
66
|
+
return { key: "valueString", value: args["value_string"] };
|
|
67
|
+
case "number":
|
|
68
|
+
if (typeof args["value_number"] !== "number") {
|
|
69
|
+
throw new ApiError("VALIDATION_FAILED", "Flag type is number — pass `value_number`.", 422);
|
|
70
|
+
}
|
|
71
|
+
return { key: "valueNumber", value: args["value_number"] };
|
|
72
|
+
case "json":
|
|
73
|
+
if (typeof args["value_json"] !== "object" ||
|
|
74
|
+
args["value_json"] === null ||
|
|
75
|
+
Array.isArray(args["value_json"])) {
|
|
76
|
+
throw new ApiError("VALIDATION_FAILED", "Flag type is json — pass `value_json` as an object.", 422);
|
|
77
|
+
}
|
|
78
|
+
return { key: "valueJson", value: args["value_json"] };
|
|
79
|
+
default:
|
|
80
|
+
throw new ApiError("VALIDATION_FAILED", `Unknown flag type '${type}'.`, 422);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export async function handler(args) {
|
|
84
|
+
const projectId = requireProjectId(args["project"] ?? args["project_id"]);
|
|
85
|
+
const flagArg = (args["flag"] ?? args["flag_id"]);
|
|
86
|
+
if (typeof flagArg !== "string" || flagArg.trim() === "") {
|
|
87
|
+
throw new Error("`flag` (key or id) is required.");
|
|
88
|
+
}
|
|
89
|
+
const envArg = (args["environment"] ?? args["environment_id"]);
|
|
90
|
+
if (typeof envArg !== "string" || envArg.trim() === "") {
|
|
91
|
+
throw new Error("`environment` (slug or id) is required.");
|
|
92
|
+
}
|
|
93
|
+
const confirm = args["confirm"] === true;
|
|
94
|
+
const flag = await resolveFlag(projectId, flagArg.trim());
|
|
95
|
+
const env = await resolveEnvironment(projectId, envArg.trim());
|
|
96
|
+
if (env.is_sensitive && !confirm) {
|
|
97
|
+
throw new SensitiveConfirmRequiredError(env.slug);
|
|
98
|
+
}
|
|
99
|
+
const effectiveType = typeof args["type"] === "string" ? args["type"] : flag.type;
|
|
100
|
+
const { key: valueKey, value } = pickTypedValue(effectiveType, args);
|
|
101
|
+
const valuesBefore = await callApi(`/admin/projects/${encodeURIComponent(projectId)}/flags/${encodeURIComponent(flag.id)}/values`);
|
|
102
|
+
const beforeRow = valuesBefore.values.find((v) => v.environmentId === env.id);
|
|
103
|
+
const body = {
|
|
104
|
+
type: effectiveType,
|
|
105
|
+
[valueKey]: value,
|
|
106
|
+
};
|
|
107
|
+
if (confirm)
|
|
108
|
+
body["confirmed"] = true;
|
|
109
|
+
const result = await callApi(`/admin/projects/${encodeURIComponent(projectId)}/flags/${encodeURIComponent(flag.id)}/values/${encodeURIComponent(env.id)}`, "PUT", body);
|
|
110
|
+
const after = result.value;
|
|
111
|
+
const before = beforeRow
|
|
112
|
+
? { type: beforeRow.type, value: beforeRow.value }
|
|
113
|
+
: null;
|
|
114
|
+
const changed = JSON.stringify(before?.value) !== JSON.stringify(after.value);
|
|
115
|
+
invalidateFlagCache(projectId);
|
|
116
|
+
return okResult({
|
|
117
|
+
value: after,
|
|
118
|
+
before,
|
|
119
|
+
after: { type: after.type, value: after.value },
|
|
120
|
+
changed,
|
|
121
|
+
environment: { id: env.id, slug: env.slug, is_sensitive: Boolean(env.is_sensitive) },
|
|
122
|
+
}, `${flag.key} in ${env.slug} = ${JSON.stringify(after.value)}${changed ? "" : " (no change)"}${env.is_sensitive ? " [sensitive]" : ""}`);
|
|
123
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type McpToolResult } from "../result.js";
|
|
2
|
+
export declare const toolDefinition: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
annotations: {
|
|
6
|
+
destructiveHint: boolean;
|
|
7
|
+
idempotentHint: boolean;
|
|
8
|
+
};
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object";
|
|
11
|
+
properties: {
|
|
12
|
+
project: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
project_id: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
flag: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
flag_id: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
name: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
description: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
required: never[];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export declare function handler(args: Record<string, unknown>): Promise<McpToolResult>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { callApi } from "../api.js";
|
|
2
|
+
import { requireProjectId } from "../config.js";
|
|
3
|
+
import { okResult } from "../result.js";
|
|
4
|
+
import { invalidateFlagCache, resolveFlag } from "../resolvers.js";
|
|
5
|
+
export const toolDefinition = {
|
|
6
|
+
name: "update_flag",
|
|
7
|
+
description: "Update a flag's name and/or description. `key` and `type` are immutable and cannot be changed here.\n" +
|
|
8
|
+
"Use when: renaming a flag or editing its description. To change its on/off value use set_flag_value.\n" +
|
|
9
|
+
"Prerequisites: a project + an existing flag (key or id). Provide at least one of name/description.\n" +
|
|
10
|
+
"Side effects: mutates flag metadata. Idempotent — same body yields changed:false.\n" +
|
|
11
|
+
'Example: update_flag(flag: "dark-mode", name: "Dark Theme").',
|
|
12
|
+
annotations: {
|
|
13
|
+
destructiveHint: false,
|
|
14
|
+
idempotentHint: true,
|
|
15
|
+
},
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
project: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Project id or slug. Optional if GREENFLAGS_PROJECT_ID is set.",
|
|
22
|
+
},
|
|
23
|
+
project_id: { type: "string", description: "Legacy alias for `project`." },
|
|
24
|
+
flag: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Flag key (preferred) or flag id.",
|
|
27
|
+
},
|
|
28
|
+
flag_id: { type: "string", description: "Legacy alias for `flag`." },
|
|
29
|
+
name: { type: "string", description: "New name (optional)." },
|
|
30
|
+
description: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "New description (optional, pass null to clear).",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: [],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export async function handler(args) {
|
|
39
|
+
const projectId = requireProjectId(args["project"] ?? args["project_id"]);
|
|
40
|
+
const flagArg = (args["flag"] ?? args["flag_id"]);
|
|
41
|
+
if (typeof flagArg !== "string" || flagArg.trim() === "") {
|
|
42
|
+
throw new Error("`flag` (key or id) is required.");
|
|
43
|
+
}
|
|
44
|
+
const name = args["name"];
|
|
45
|
+
const descriptionRaw = args["description"];
|
|
46
|
+
if (name === undefined && descriptionRaw === undefined) {
|
|
47
|
+
throw new Error("At least one of `name` or `description` is required.");
|
|
48
|
+
}
|
|
49
|
+
const current = await resolveFlag(projectId, flagArg.trim());
|
|
50
|
+
const body = {
|
|
51
|
+
name: typeof name === "string" ? name : current.name,
|
|
52
|
+
description: descriptionRaw === undefined ? current.description ?? null : descriptionRaw,
|
|
53
|
+
};
|
|
54
|
+
const data = await callApi(`/admin/projects/${encodeURIComponent(projectId)}/flags/${encodeURIComponent(current.id)}`, "PATCH", body);
|
|
55
|
+
invalidateFlagCache(projectId);
|
|
56
|
+
const before = { name: current.name, description: current.description ?? null };
|
|
57
|
+
const after = data.flag;
|
|
58
|
+
const changed = before.name !== after.name || before.description !== (after.description ?? null);
|
|
59
|
+
return okResult({ flag: after, before, changed }, `Flag ${after.key} updated${changed ? "" : " (no changes)"}.`);
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@greenflags/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for GreenFlags feature flag management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"greenflags-mcp": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"feature-flags",
|
|
17
|
+
"greenflags",
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.build.json",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"prepublishOnly": "npm run typecheck && npm run build && npm test"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"axios": "^1.9.0",
|
|
31
|
+
"open": "^10.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"typescript": "^5.8.0",
|
|
36
|
+
"vitest": "^2.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|