Guide · Feature Flags
MCP Tools for Flagsmith — Feature states, remote config, identity-based evaluation, segments, and health monitoring
Flagsmith combines two distinct capabilities in one platform: feature flags (boolean on/off per environment) and remote configuration (arbitrary string values stored alongside each flag). MCP tools that treat Flagsmith as a pure on/off system miss the remote config dimension entirely — a flag can be "enabled" with an empty value, returning the feature toggle's boolean state but discarding the configuration payload that controls how that feature behaves. The key field is feature_state_value, not just enabled, and they change independently.
TL;DR
API base: https://api.flagsmith.com/api/v1/ (or self-hosted URL). Auth: X-Environment-Key: <env-key> for environment reads (SDK operations); Authorization: Api-Key <api-key> for management reads. Get all flags: GET /flags/. Get identity flags: POST /identities/ with {"identifier": "user-123", "traits": [...]}. Health: GET /health/ → {"status": "ok"}. Each flag response has two fields that matter: enabled (boolean) and feature_state_value (string/null — the remote config value). Always read both.
Flagsmith architecture for MCP tool authors
Flagsmith organises state into: Organisation → Project → Environment → Feature → Feature State. A feature is defined at the project level (it exists once), but its state — whether it's enabled and what value it carries — is defined per environment. You never query features directly; you query the feature state in a specific environment.
Two credential types control different access levels:
- Environment API key (format:
ser.xxxfor server-side): used withX-Environment-Keyheader. Scoped to a single environment. Used for SDK operations: listing flags, retrieving identity states. This is what server-side SDKs use. - API key (Personal or Service Account key): used with
Authorization: Api-Key xxx. Used for management: creating flags, reading across environments, accessing audit logs. Broader scope.
// Environment-scoped reads (SDK-level, most MCP tools use this)
async function flagsmithEnv(envKey, baseUrl, path, method = 'GET', body = null) {
const base = (baseUrl || 'https://api.flagsmith.com').replace(/\/$/, '');
const res = await fetch(`${base}/api/v1${path}`, {
method,
headers: {
'X-Environment-Key': envKey,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : null,
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`Flagsmith ENV API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
}
return res.json();
}
// Management-level reads (project/org scope)
async function flagsmithMgmt(apiKey, baseUrl, path) {
const base = (baseUrl || 'https://api.flagsmith.com').replace(/\/$/, '');
const res = await fetch(`${base}/api/v1${path}`, {
headers: {
'Authorization': `Api-Key ${apiKey}`,
'Content-Type': 'application/json',
},
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) {
throw new Error(`Flagsmith MGMT API GET ${path} → ${res.status} ${res.statusText}`);
}
return res.json();
}
Flags vs remote config: reading both dimensions
Every Flagsmith feature state has two independent dimensions: the enabled boolean and the feature_state_value string. A feature named max_batch_size might be enabled with a value of "500" in production and enabled with "50" in staging — the enabled state is the same, but the behavior differs by value. MCP tools must surface both fields explicitly.
// Get all flags for the current environment
async function getFlags(envKey, baseUrl) {
const flags = await flagsmithEnv(envKey, baseUrl, '/flags/');
return flags.map(flag => ({
featureName: flag.feature.name,
featureId: flag.feature.id,
featureType: flag.feature.type, // STANDARD (boolean) | MULTIVARIATE
enabled: flag.enabled,
value: flag.feature_state_value, // string | number | null — the remote config value
// Critical: value can be non-null even when enabled=false
// (the value is stored regardless of the enabled state)
}));
}
// Get a specific flag by name
async function getFlag(envKey, baseUrl, featureName) {
const flags = await getFlags(envKey, baseUrl);
const flag = flags.find(f => f.featureName === featureName);
if (!flag) {
return { found: false, enabled: false, value: null };
}
return { found: true, ...flag };
}
// Parse the value with type awareness
function parseValue(rawValue, expectedType = 'string') {
if (rawValue === null || rawValue === undefined) return null;
switch (expectedType) {
case 'number': return Number(rawValue);
case 'boolean': return rawValue === 'true' || rawValue === true;
case 'json': {
try { return JSON.parse(rawValue); }
catch { return null; }
}
default: return String(rawValue);
}
}
Identity-based evaluation with traits
The most powerful Flagsmith capability is identity-based evaluation: instead of environment-level flags (same for all users), you POST an identity with traits to get per-user flag states. Traits are key-value pairs associated with an identity that targeting rules (segments) evaluate against. An identity that matches a segment gets the segment's overridden flag state, which can differ from the environment default.
// Get flags for a specific user identity with their traits
async function getIdentityFlags(envKey, baseUrl, identifier, traits = []) {
// POST to /identities/ creates or updates the identity and returns flags
const result = await flagsmithEnv(envKey, baseUrl, '/identities/', 'POST', {
identifier,
traits: traits.map(t => ({
trait_key: t.key,
trait_value: t.value, // string | number | boolean
})),
});
return {
identity: result.identifier,
flags: (result.flags ?? []).map(flag => ({
featureName: flag.feature.name,
enabled: flag.enabled,
value: flag.feature_state_value,
// Identity flags that differ from env defaults got here via segment override
})),
traits: result.traits ?? [],
};
}
// Update traits for an existing identity (without re-fetching flags)
async function setTrait(envKey, baseUrl, identifier, traitKey, traitValue) {
return flagsmithEnv(envKey, baseUrl, '/traits/', 'POST', {
identity: { identifier },
trait_key: traitKey,
trait_value: traitValue,
});
}
// Example: get a specific flag for a user with context
async function evaluateForUser(envKey, baseUrl, identifier, featureName, userTraits = []) {
const result = await getIdentityFlags(envKey, baseUrl, identifier, userTraits);
const flag = result.flags.find(f => f.featureName === featureName);
return flag ?? { featureName, enabled: false, value: null };
}
A critical behavior: calling POST /identities/ creates the identity if it doesn't exist and persists the traits you provide. This is not a stateless read — every call can mutate server-side state. For read-only evaluation probes, use GET /identities/?identifier=user-123 to read without creating or updating traits.
Segments and multivariate flags
Segments are named groups of users defined by trait-based rules. A segment rule example: "plan equals enterprise AND country in [US, CA, GB]". When an identity's traits match a segment, any flag that has a segment override for that segment returns the overridden state for that identity. MCP tools can list segments to show which user groups have distinct flag behavior.
// List segments defined in a project (requires management API key)
async function listSegments(apiKey, baseUrl, projectId) {
const data = await flagsmithMgmt(apiKey, baseUrl, `/projects/${projectId}/segments/`);
return (data.results ?? []).map(s => ({
id: s.id,
name: s.name,
description: s.description,
rules: s.rules, // nested rule tree: {type: 'ALL'|'ANY'|'NONE', rules: [{property, operator, value}]}
}));
}
// Multivariate flags: instead of a single enabled+value, the flag has
// multiple variations each with a percentage weight
async function getMultivariateFlag(envKey, baseUrl, featureName) {
// For multivariate flags, the /flags/ response has a different shape
const flags = await flagsmithEnv(envKey, baseUrl, '/flags/');
const flag = flags.find(f => f.feature.name === featureName);
if (!flag) return null;
if (flag.feature.type !== 'MULTIVARIATE') {
return { type: 'standard', enabled: flag.enabled, value: flag.feature_state_value };
}
// For multivariate, get the variation options separately
const mvOptions = await flagsmithEnv(
envKey, baseUrl,
`/features/featurestates/?feature=${flag.feature.id}`
);
return {
type: 'multivariate',
enabled: flag.enabled,
controlValue: flag.feature_state_value, // the control variation value
variations: (mvOptions.results ?? []).filter(o => o.multivariate_feature_option !== null),
};
}
Health check: what GET /health/ misses
Flagsmith's GET /health/ returns {"status": "ok"} when the process is running. It does not verify: whether the database is responding, whether identity evaluation (the segment matching engine) is functioning, or whether flag state changes are being persisted. Self-hosted Flagsmith deployments using PostgreSQL are particularly vulnerable to the "process up, DB read replicas stale" failure mode that affects many stateful services.
async function healthCheck(envKey, apiKey, baseUrl, projectId) {
const results = await Promise.allSettled([
// 1. Process liveness
(async () => {
const res = await fetch(`${(baseUrl || 'https://api.flagsmith.com').replace(/\/$/, '')}/health/`);
const data = await res.json();
if (data.status !== 'ok') throw new Error(`Health status: ${data.status}`);
return { kind: 'process', status: data.status };
})(),
// 2. Flag store readable (exercises DB read path)
(async () => {
const flags = await flagsmithEnv(envKey, baseUrl, '/flags/');
return {
kind: 'flag_store',
flagCount: flags.length,
};
})(),
// 3. Identity evaluation (exercises segment matching engine)
(async () => {
const result = await getIdentityFlags(
envKey, baseUrl,
'__alivemcp_health_probe__',
[{ key: 'probe', value: 'true' }]
);
return {
kind: 'identity_eval',
flagCount: result.flags.length,
traitsAccepted: true,
};
})(),
// 4. Management API (exercises org/project read path — requires apiKey)
...(apiKey ? [(async () => {
const projects = await flagsmithMgmt(apiKey, baseUrl, '/projects/');
return {
kind: 'mgmt_api',
projectCount: (projects.results ?? projects).length,
};
})()] : []),
]);
return {
healthy: results.every(r => r.status === 'fulfilled'),
components: results.map(r =>
r.status === 'fulfilled'
? { ...r.value, ok: true }
: { ok: false, error: r.reason?.message }
),
};
}
The identity evaluation probe is the most comprehensive check: it creates a synthetic identity with a probe trait, then retrieves flags for it. If segment matching is broken (e.g., a bad migration corrupted segment rules), identity evaluation returns incorrect results while the flags endpoint returns correctly. The probe identity __alivemcp_health_probe__ should have no segment overrides — if it returns flags that differ from environment defaults, that's a signal of unexpected targeting state.
Set AliveMCP alert thresholds: critical when the flags endpoint returns an error or the process health endpoint returns anything other than ok; warning when flag count drops more than 10% from baseline (possible bulk deletion) or identity evaluation takes more than 2 seconds (segment matching engine degraded).
Common integration pitfalls
- Environment key vs API key scope confusion
- The Environment key (
ser.xxx) is scoped to one environment. Using it to list flags gives only that environment's flags — not all environments. If a flag was added to the project after the Environment key was created, it appears inGET /flags/with the environment's default state (disabled, null value), not an error. There's no way to know a flag exists in other environments without the management API. - POST /identities/ creates and persists identities
- Unlike LaunchDarkly's context evaluation (stateless),
POST /identities/in Flagsmith persists the identity and its traits in the database. Every call is a write. UseGET /identities/?identifier=xxxfor read-only access to existing identities. In high-traffic scenarios, repeatedly posting the same identity with the same traits adds unnecessary write load to Flagsmith's database. - feature_state_value is always a string
- Even when you configured a flag value as a number in the Flagsmith UI,
feature_state_valuereturns a string ("42", not42). MCP tools must parse to the expected type explicitly. Passing a string"false"to a boolean check fails silently —Boolean("false") === truein JavaScript. - Segment changes are not retroactive on cached SDKs
- Server-side Flagsmith SDKs cache flag states on initialization and refresh on an interval. If a user's traits don't match any segment at cache time but later would match (because a new segment was added), the SDK won't reflect this until the next refresh cycle. Use
POST /identities/directly (server-to-server, not cached) for real-time segment evaluation.
Related guides
- MCP tools for LaunchDarkly — flag evaluation, targeting rules, and SDK vs REST API
- MCP tools for Unleash — open-source feature toggles, activation strategies, and Admin vs Client API
- MCP tools for OpenFeature — provider pattern, evaluation context, hooks, and typed flag evaluation
- MCP tools for Split.io — treatments, traffic allocation, dynamic config, and experiment tracking
- MCP server health check patterns — composite endpoints and failure classification
- MCP server uptime monitoring — probes, intervals, and alert routing