@greenflags/mcp 0.2.0 → 0.2.1

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/dist/index.js CHANGED
@@ -23,7 +23,7 @@ const tools = [
23
23
  setFlagValue,
24
24
  archiveFlag,
25
25
  ];
26
- const server = new Server({ name: "greenflags", version: "0.2.0" }, { capabilities: { tools: {} } });
26
+ const server = new Server({ name: "greenflags", version: "0.2.1" }, { capabilities: { tools: {} } });
27
27
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
28
28
  tools: tools.map((t) => t.toolDefinition),
29
29
  }));
@@ -12,6 +12,12 @@ export interface EnvironmentRecord {
12
12
  slug: string;
13
13
  is_sensitive?: boolean;
14
14
  }
15
+ export interface ProjectRecord {
16
+ id: string;
17
+ name: string;
18
+ slug: string;
19
+ }
20
+ export declare function resolveProjectId(projectOrSlug: string): Promise<string>;
15
21
  export declare function resolveFlagId(projectId: string, flagOrKey: string): Promise<string>;
16
22
  export declare function resolveFlag(projectId: string, flagOrKey: string): Promise<FlagRecord>;
17
23
  export declare function resolveEnvironmentId(projectId: string, envOrSlug: string): Promise<string>;
@@ -20,3 +26,4 @@ export declare function listAllFlags(projectId: string): Promise<FlagRecord[]>;
20
26
  export declare function listAllEnvironments(projectId: string): Promise<EnvironmentRecord[]>;
21
27
  export declare function invalidateFlagCache(projectId: string): void;
22
28
  export declare function invalidateEnvironmentCache(projectId: string): void;
29
+ export declare function invalidateProjectCache(): void;
package/dist/resolvers.js CHANGED
@@ -3,6 +3,9 @@ import { ApiError } from "./errors.js";
3
3
  const TTL_MS = 60_000;
4
4
  const flagCache = new Map();
5
5
  const envCache = new Map();
6
+ // Projects are workspace-level (not scoped by a parent id), so there's a
7
+ // single cache entry rather than one per key like flags/environments.
8
+ let projectCache;
6
9
  function fresh(entry) {
7
10
  if (!entry)
8
11
  return undefined;
@@ -44,6 +47,26 @@ async function loadEnvironments(projectId) {
44
47
  envCache.set(projectId, entry);
45
48
  return entry;
46
49
  }
50
+ async function loadProjects() {
51
+ const data = await callApi("/admin/projects");
52
+ const byId = new Map();
53
+ const byAlt = new Map();
54
+ for (const p of data.projects) {
55
+ byId.set(p.id, p);
56
+ byAlt.set(p.slug, p);
57
+ }
58
+ projectCache = { at: Date.now(), byId, byAlt };
59
+ return projectCache;
60
+ }
61
+ export async function resolveProjectId(projectOrSlug) {
62
+ const entry = fresh(projectCache) ?? (await loadProjects());
63
+ if (entry.byId.has(projectOrSlug))
64
+ return projectOrSlug;
65
+ const hit = entry.byAlt.get(projectOrSlug);
66
+ if (hit)
67
+ return hit.id;
68
+ throw new ApiError("PROJECT_NOT_FOUND", `Project '${projectOrSlug}' not found in this workspace. Use list_projects to see available projects.`, 404);
69
+ }
47
70
  export async function resolveFlagId(projectId, flagOrKey) {
48
71
  const entry = fresh(flagCache.get(projectId)) ?? (await loadFlags(projectId));
49
72
  if (entry.byId.has(flagOrKey))
@@ -90,3 +113,6 @@ export function invalidateFlagCache(projectId) {
90
113
  export function invalidateEnvironmentCache(projectId) {
91
114
  envCache.delete(projectId);
92
115
  }
116
+ export function invalidateProjectCache() {
117
+ projectCache = undefined;
118
+ }
@@ -1,7 +1,7 @@
1
1
  import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { okResult } from "../result.js";
4
- import { invalidateFlagCache, resolveFlag } from "../resolvers.js";
4
+ import { invalidateFlagCache, resolveFlag, resolveProjectId } from "../resolvers.js";
5
5
  export const toolDefinition = {
6
6
  name: "archive_flag",
7
7
  description: "Archive a flag (soft-delete: stops being served but kept for history).\n" +
@@ -30,7 +30,7 @@ export const toolDefinition = {
30
30
  },
31
31
  };
32
32
  export async function handler(args) {
33
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
33
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
34
34
  const flagArg = (args["flag"] ?? args["flag_id"]);
35
35
  if (typeof flagArg !== "string" || flagArg.trim() === "") {
36
36
  throw new Error("`flag` (key or id) is required.");
@@ -1,7 +1,7 @@
1
1
  import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { okResult } from "../result.js";
4
- import { invalidateFlagCache } from "../resolvers.js";
4
+ import { invalidateFlagCache, resolveProjectId } from "../resolvers.js";
5
5
  export const toolDefinition = {
6
6
  name: "create_flag",
7
7
  description: "Create a new feature flag in a project. Defaults to a boolean flag; pass `type` for string/number/json.\n" +
@@ -37,7 +37,7 @@ export const toolDefinition = {
37
37
  },
38
38
  };
39
39
  export async function handler(args) {
40
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
40
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
41
41
  const key = String(args["key"] ?? "").trim();
42
42
  const name = String(args["name"] ?? "").trim();
43
43
  if (!key || !name)
@@ -1,7 +1,7 @@
1
1
  import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { okResult } from "../result.js";
4
- import { listAllFlags } from "../resolvers.js";
4
+ import { listAllFlags, resolveProjectId } from "../resolvers.js";
5
5
  export const toolDefinition = {
6
6
  name: "get_flag",
7
7
  description: "Get a single flag's metadata (id, key, name, type, status, description) by key (preferred) or id.\n" +
@@ -31,7 +31,7 @@ export const toolDefinition = {
31
31
  },
32
32
  };
33
33
  export async function handler(args) {
34
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
34
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
35
35
  const flagArg = (args["flag"] ?? args["flag_key"]);
36
36
  if (typeof flagArg !== "string" || flagArg.trim() === "") {
37
37
  throw new Error("`flag` (key or id) is required.");
@@ -1,7 +1,7 @@
1
1
  import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { okResult } from "../result.js";
4
- import { listAllEnvironments, resolveEnvironmentId, resolveFlag, } from "../resolvers.js";
4
+ import { listAllEnvironments, resolveEnvironmentId, resolveFlag, resolveProjectId, } from "../resolvers.js";
5
5
  export const toolDefinition = {
6
6
  name: "get_flag_values",
7
7
  description: "Get a flag's value across all environments, or a single environment when `environment` is given.\n" +
@@ -34,7 +34,7 @@ export const toolDefinition = {
34
34
  },
35
35
  };
36
36
  export async function handler(args) {
37
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
37
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
38
38
  const flagArg = (args["flag"] ?? args["flag_key"]);
39
39
  if (typeof flagArg !== "string" || flagArg.trim() === "") {
40
40
  throw new Error("`flag` (key or id) is required.");
@@ -1,6 +1,6 @@
1
1
  import { requireProjectId } from "../config.js";
2
2
  import { okResult } from "../result.js";
3
- import { listAllEnvironments } from "../resolvers.js";
3
+ import { listAllEnvironments, resolveProjectId } from "../resolvers.js";
4
4
  export const toolDefinition = {
5
5
  name: "list_environments",
6
6
  description: "List all environments in a project (e.g. dev, staging, prod), each with id, slug, and is_sensitive.\n" +
@@ -24,7 +24,7 @@ export const toolDefinition = {
24
24
  },
25
25
  };
26
26
  export async function handler(args) {
27
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
27
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
28
28
  const envs = await listAllEnvironments(projectId);
29
29
  const items = envs.map((e) => ({
30
30
  id: e.id,
@@ -1,7 +1,7 @@
1
1
  import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { okResult } from "../result.js";
4
- import { listAllFlags, resolveEnvironment, } from "../resolvers.js";
4
+ import { listAllFlags, resolveEnvironment, resolveProjectId, } from "../resolvers.js";
5
5
  export const toolDefinition = {
6
6
  name: "list_flags",
7
7
  description: "List flags in a project, optionally filtered by status/search and showing each flag's value in one environment.\n" +
@@ -41,7 +41,7 @@ export const toolDefinition = {
41
41
  },
42
42
  };
43
43
  export async function handler(args) {
44
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
44
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
45
45
  const status = typeof args["status"] === "string" ? args["status"] : undefined;
46
46
  const search = typeof args["search"] === "string" && args["search"].trim() !== ""
47
47
  ? args["search"].toLowerCase().trim()
@@ -2,7 +2,7 @@ import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { ApiError, SensitiveConfirmRequiredError } from "../errors.js";
4
4
  import { okResult } from "../result.js";
5
- import { invalidateFlagCache, resolveEnvironment, resolveFlag, } from "../resolvers.js";
5
+ import { invalidateFlagCache, resolveEnvironment, resolveFlag, resolveProjectId, } from "../resolvers.js";
6
6
  export const toolDefinition = {
7
7
  name: "set_flag_value",
8
8
  description: "Set a flag's value in one environment. Supports boolean/string/number/json (replaces the old toggle_flag_value).\n" +
@@ -81,7 +81,7 @@ function pickTypedValue(type, args) {
81
81
  }
82
82
  }
83
83
  export async function handler(args) {
84
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
84
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
85
85
  const flagArg = (args["flag"] ?? args["flag_id"]);
86
86
  if (typeof flagArg !== "string" || flagArg.trim() === "") {
87
87
  throw new Error("`flag` (key or id) is required.");
@@ -1,7 +1,7 @@
1
1
  import { callApi } from "../api.js";
2
2
  import { requireProjectId } from "../config.js";
3
3
  import { okResult } from "../result.js";
4
- import { invalidateFlagCache, resolveFlag } from "../resolvers.js";
4
+ import { invalidateFlagCache, resolveFlag, resolveProjectId } from "../resolvers.js";
5
5
  export const toolDefinition = {
6
6
  name: "update_flag",
7
7
  description: "Update a flag's name and/or description. `key` and `type` are immutable and cannot be changed here.\n" +
@@ -36,7 +36,7 @@ export const toolDefinition = {
36
36
  },
37
37
  };
38
38
  export async function handler(args) {
39
- const projectId = requireProjectId(args["project"] ?? args["project_id"]);
39
+ const projectId = await resolveProjectId(requireProjectId(args["project"] ?? args["project_id"]));
40
40
  const flagArg = (args["flag"] ?? args["flag_id"]);
41
41
  if (typeof flagArg !== "string" || flagArg.trim() === "") {
42
42
  throw new Error("`flag` (key or id) is required.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greenflags/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server for GreenFlags feature flag management",
5
5
  "type": "module",
6
6
  "engines": {