Skip to content

Commit 13f36db

Browse files
committed
Merge branch 'main' of github.com:Flagsmith/flagsmith into docs/backend-frontend-agents-context
2 parents 5b6012e + 6fe6265 commit 13f36db

63 files changed

Lines changed: 1356 additions & 151 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Thanks for submitting a PR! Please check the boxes below:
22

3-
- [ ] I have read the [Contributing Guide](/CONTRIBUTING.md).
3+
- [ ] I have read the [Contributing Guide](/Flagsmith/flagsmith/blob/main/CONTRIBUTING.md).
44
- [ ] I have added information to `docs/` if required so people know about the feature.
55
- [ ] I have filled in the "Changes" section below.
66
- [ ] I have filled in the "How did you test this code" section below.

.github/workflows/api-deploy-production-ecs.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,42 @@ jobs:
1919
with:
2020
environment: production
2121
secrets: inherit
22+
23+
mcp-schema-push:
24+
name: Push MCP Schema to Gram
25+
runs-on: depot-ubuntu-latest
26+
defaults:
27+
run:
28+
working-directory: api
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- name: Install Poetry
33+
run: make install-poetry
34+
35+
- name: Set up Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: "3.12"
39+
cache: poetry
40+
41+
- name: Install dependencies
42+
run: |
43+
echo "https://${{ secrets.GH_PRIVATE_ACCESS_TOKEN }}:@github.com" > ${HOME}/.git-credentials
44+
git config --global credential.helper store
45+
make install-packages opts="--with saml,auth-controller,workflows,release-pipelines"
46+
make install-private-modules
47+
rm -rf ${HOME}/.git-credentials
48+
49+
- name: Generate MCP schema
50+
run: make generate-mcp-spec
51+
52+
- name: Install Gram CLI
53+
run: curl -fsSL https://go.getgram.ai/cli.sh | bash
54+
55+
- name: Push to Gram
56+
env:
57+
GRAM_API_KEY: ${{ secrets.GRAM_API_KEY }}
58+
GRAM_ORG: ${{ vars.GRAM_ORG }}
59+
GRAM_PROJECT: ${{ vars.GRAM_PROJECT }}
60+
run: gram push --api-key "$GRAM_API_KEY" --org "$GRAM_ORG" --project "$GRAM_PROJECT" --config gram.json

.github/workflows/api-pull-request.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
name: API Pull Request
22

3+
permissions:
4+
contents: read # For actions/checkout
5+
id-token: write # For Codecov OIDC
6+
37
on:
48
pull_request:
59
paths:
@@ -80,5 +84,5 @@ jobs:
8084
env:
8185
PYTHON: ${{ matrix.python-version }}
8286
with:
83-
token: ${{ secrets.CODECOV_TOKEN }}
8487
env_vars: PYTHON
88+
use_oidc: true

.github/workflows/api-tests-with-private-packages.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
name: Run API tests with private package
1+
name: API Pull Request with Private Packages
2+
3+
permissions:
4+
contents: read # For actions/checkout
5+
id-token: write # For Codecov OIDC
26

37
on:
48
pull_request:
@@ -24,7 +28,12 @@ defaults:
2428
jobs:
2529
test:
2630
runs-on: depot-ubuntu-latest-16
27-
name: API Tests
31+
name: API Unit Tests
32+
33+
strategy:
34+
max-parallel: 2
35+
matrix:
36+
python-version: ["3.11", "3.12"]
2837

2938
steps:
3039
- name: Cloning repo
@@ -35,7 +44,7 @@ jobs:
3544

3645
- uses: actions/setup-python@v5
3746
with:
38-
python-version: "3.12"
47+
python-version: ${{ matrix.python-version }}
3948
cache: poetry
4049

4150
- name: Install SAML Dependencies
@@ -55,3 +64,12 @@ jobs:
5564
env:
5665
DOTENV_OVERRIDE_FILE: .env-ci
5766
run: make test
67+
68+
- name: Upload Coverage
69+
uses: codecov/codecov-action@v4
70+
env:
71+
PRIVATE_PACKAGES: "true"
72+
PYTHON: ${{ matrix.python-version }}
73+
with:
74+
env_vars: PRIVATE_PACKAGES,PYTHON
75+
use_oidc: true

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.210.0"
2+
".": "2.211.5"
33
}

CHANGELOG.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,76 @@
11
# Changelog
22

3+
## [2.211.5](https://github.com/Flagsmith/flagsmith/compare/v2.211.4...v2.211.5) (2026-01-28)
4+
5+
6+
### Bug Fixes
7+
8+
* **api-docs:** Avoid `readOnly` properties with `required` ([#6608](https://github.com/Flagsmith/flagsmith/issues/6608)) ([31ee000](https://github.com/Flagsmith/flagsmith/commit/31ee000451172bdc33d3909f0e8a6f51325e3216))
9+
10+
## [2.211.4](https://github.com/Flagsmith/flagsmith/compare/v2.211.3...v2.211.4) (2026-01-27)
11+
12+
13+
### Bug Fixes
14+
15+
* display correct control value in identity override variation list ([#6585](https://github.com/Flagsmith/flagsmith/issues/6585)) ([93ffc6c](https://github.com/Flagsmith/flagsmith/commit/93ffc6cb5076960c456e0227da72d5c9b70f1d3f))
16+
17+
## [2.211.3](https://github.com/Flagsmith/flagsmith/compare/v2.211.2...v2.211.3) (2026-01-26)
18+
19+
20+
### Bug Fixes
21+
22+
* add loading state for MultiSelect in Create Segment modal ([#6565](https://github.com/Flagsmith/flagsmith/issues/6565)) ([8fd3be3](https://github.com/Flagsmith/flagsmith/commit/8fd3be34d5e33fefd1d7ced54020f5f451796cbc))
23+
* **frontend:** add docs link to LaunchDarkly import screen ([#6587](https://github.com/Flagsmith/flagsmith/issues/6587)) ([2881817](https://github.com/Flagsmith/flagsmith/commit/28818175a28bf18f68f7721e736f2c2e92341ef6))
24+
* **frontend:** hide 'Enabled by default' toggle when editing identity override ([#6584](https://github.com/Flagsmith/flagsmith/issues/6584)) ([141d69c](https://github.com/Flagsmith/flagsmith/commit/141d69cf923bae3bb676dfc5fa532feb7489103a))
25+
26+
27+
### Dependency Updates
28+
29+
* bump influxdb-client from 1.28.0 to 1.50.0 in /api ([#6593](https://github.com/Flagsmith/flagsmith/issues/6593)) ([da17d1a](https://github.com/Flagsmith/flagsmith/commit/da17d1ad03ff1c77b145e540572c43e207ac5c2a))
30+
31+
## [2.211.2](https://github.com/Flagsmith/flagsmith/compare/v2.211.1...v2.211.2) (2026-01-22)
32+
33+
34+
### Infrastructure (Flagsmith SaaS Only)
35+
36+
* **Monitoring:** Expose production /metrics to :9100 ([#6578](https://github.com/Flagsmith/flagsmith/issues/6578)) ([783b762](https://github.com/Flagsmith/flagsmith/commit/783b762911f258e306f54a3aabefb996908c540e))
37+
38+
## [2.211.1](https://github.com/Flagsmith/flagsmith/compare/v2.211.0...v2.211.1) (2026-01-22)
39+
40+
41+
### Dependency Updates
42+
43+
* bump diff from 5.2.0 to 5.2.2 in /docs ([#6564](https://github.com/Flagsmith/flagsmith/issues/6564)) ([9643968](https://github.com/Flagsmith/flagsmith/commit/96439684727e4790e2ebd4ce50f7e8d7d1184909))
44+
45+
## [2.211.0](https://github.com/Flagsmith/flagsmith/compare/v2.210.0...v2.211.0) (2026-01-21)
46+
47+
48+
### Features
49+
50+
* Edit Change Requests ([#6356](https://github.com/Flagsmith/flagsmith/issues/6356)) ([00085cd](https://github.com/Flagsmith/flagsmith/commit/00085cdefc6266174c5e1e10e8cfb36b402c4d8e))
51+
* **frontend:** add Jest unit testing infrastructure ([#6432](https://github.com/Flagsmith/flagsmith/issues/6432)) ([9468354](https://github.com/Flagsmith/flagsmith/commit/9468354eb1a26541cfcf1048fc2ede39e014a1b1))
52+
* **frontend:** add reusable EmptyState component for empty pages ([#6538](https://github.com/Flagsmith/flagsmith/issues/6538)) ([cd64f1d](https://github.com/Flagsmith/flagsmith/commit/cd64f1de4aa2594f7b0166d2ea6b37f7af5062c2))
53+
* **frontend:** add subscription limit info to Usage and Billing pages ([#6539](https://github.com/Flagsmith/flagsmith/issues/6539)) ([7e2d9fb](https://github.com/Flagsmith/flagsmith/commit/7e2d9fb55a8c9f9fd8eaff5722281466a8d0f2c6))
54+
* synchronize-openapi-schema-with-gram ([#6499](https://github.com/Flagsmith/flagsmith/issues/6499)) ([31d64ad](https://github.com/Flagsmith/flagsmith/commit/31d64ad2c8346dd67aaf349a78dcbf79970d833d))
55+
* use-default-allocation-from-flag ([#6558](https://github.com/Flagsmith/flagsmith/issues/6558)) ([fcae6b4](https://github.com/Flagsmith/flagsmith/commit/fcae6b4e6769499a63b764ab4cd3c0c377680ffb))
56+
57+
58+
### Bug Fixes
59+
60+
* add null checks for multivariate_options in feature-list-store ([#6561](https://github.com/Flagsmith/flagsmith/issues/6561)) ([aae7f77](https://github.com/Flagsmith/flagsmith/commit/aae7f77d108d54c863f0c74d4cbe663320fc6daa))
61+
* add skip conditions for undefined environment ID queries on Compare feat ([#6557](https://github.com/Flagsmith/flagsmith/issues/6557)) ([2b832fa](https://github.com/Flagsmith/flagsmith/commit/2b832facc1e35705383b76d842c36349e349c433))
62+
* reviewed-dependencies-and-mutualize-components ([#6567](https://github.com/Flagsmith/flagsmith/issues/6567)) ([7350f3d](https://github.com/Flagsmith/flagsmith/commit/7350f3dd07c7ed44cb5a437872ead25e93d2d4c9))
63+
64+
65+
### Infrastructure (Flagsmith SaaS Only)
66+
67+
* **Monitoring:** Expose /metrics to new port ([#6533](https://github.com/Flagsmith/flagsmith/issues/6533)) ([007ff8a](https://github.com/Flagsmith/flagsmith/commit/007ff8a7f3f9ee446205d59f961e51aa98629a2c))
68+
69+
70+
### Dependency Updates
71+
72+
* bump werkzeug from 3.1.4 to 3.1.5 in /api ([#6505](https://github.com/Flagsmith/flagsmith/issues/6505)) ([86a243d](https://github.com/Flagsmith/flagsmith/commit/86a243dbe4f921cd98791fd3ae097f8dad323ce2))
73+
374
## [2.210.0](https://github.com/Flagsmith/flagsmith/compare/v2.209.1...v2.210.0) (2026-01-19)
475

576

api/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ DOTENV_OVERRIDE_FILE ?= .env
1010
POETRY_VERSION ?= 2.2.1
1111

1212
SAML_REVISION ?= v1.6.6
13-
RBAC_REVISION ?= v0.12.1
13+
RBAC_REVISION ?= v0.13.0
1414

1515
-include .env-local
1616
-include $(DOTENV_OVERRIDE_FILE)
@@ -159,3 +159,7 @@ generate-docs:
159159
.PHONY: add-known-sdk-version
160160
add-known-sdk-version:
161161
poetry run python scripts/add-known-sdk-version.py $(opts)
162+
163+
.PHONY: generate-mcp-spec
164+
generate-mcp-spec:
165+
poetry run python manage.py spectacular --generator-class api.openapi.MCPSchemaGenerator --file mcp_openapi.yaml

api/api/openapi.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Literal
1+
from typing import TYPE_CHECKING, Any, Literal
22

33
from drf_spectacular import generators, openapi
44
from drf_spectacular.extensions import (
@@ -47,11 +47,91 @@ class SchemaGenerator(generators.SchemaGenerator):
4747
Adds a `$schema` property to the root schema object.
4848
"""
4949

50+
if TYPE_CHECKING:
51+
# Parent class has no type_hints, this is used to ignore the type error upstream
52+
def __init__(self, **kwargs: Any) -> None:
53+
super().__init__(**kwargs) # type: ignore[no-untyped-call]
54+
5055
def get_schema(
5156
self, request: Request | None = None, public: bool = False
5257
) -> dict[str, Any]:
5358
schema: dict[str, Any] = super().get_schema(request, public) # type: ignore[no-untyped-call]
54-
schema["$schema"] = "https://spec.openapis.org/oas/3.1/dialect/base"
59+
return {
60+
"$schema": "https://spec.openapis.org/oas/3.1/dialect/base",
61+
**schema,
62+
}
63+
64+
65+
class MCPSchemaGenerator(SchemaGenerator):
66+
"""
67+
Schema generator that filters to only include operations tagged with "mcp".
68+
69+
Uses x-gram extension for Gram-native tool naming and descriptions.
70+
Gram reads x-gram directly from the spec.
71+
"""
72+
73+
MCP_TAG = "mcp"
74+
MCP_SERVER_URL = "https://api.flagsmith.com"
75+
76+
def get_schema(
77+
self, request: Request | None = None, public: bool = False
78+
) -> dict[str, Any]:
79+
schema = super().get_schema(request, public)
80+
schema["paths"] = self._filter_paths(schema.get("paths", {}))
81+
schema = self._update_security_for_mcp(schema)
82+
schema.pop("$schema", None)
83+
info = schema.pop("info").copy()
84+
info["title"] = "mcp_openapi"
85+
return {
86+
"openapi": schema.pop("openapi"),
87+
"info": info,
88+
"servers": [{"url": self.MCP_SERVER_URL}],
89+
**schema,
90+
}
91+
92+
def _filter_paths(self, paths: dict[str, Any]) -> dict[str, Any]:
93+
"""Filter paths to only include operations tagged with 'mcp'."""
94+
filtered_paths: dict[str, Any] = {}
95+
96+
for path, path_item in paths.items():
97+
filtered_operations: dict[str, Any] = {}
98+
has_any_mcp_tag = False
99+
100+
for method, operation in path_item.items():
101+
if not isinstance(operation, dict):
102+
filtered_operations[method] = operation
103+
continue
104+
105+
tags = operation.get("tags", [])
106+
if self.MCP_TAG in tags:
107+
filtered_operations[method] = self._transform_for_mcp(operation)
108+
has_any_mcp_tag = True
109+
110+
if has_any_mcp_tag:
111+
filtered_paths[path] = filtered_operations
112+
113+
return filtered_paths
114+
115+
def _transform_for_mcp(self, operation: dict[str, Any]) -> dict[str, Any]:
116+
"""Apply MCP-specific transformations to an operation."""
117+
operation = operation.copy()
118+
# Remove operation-level security (use global MCP security instead)
119+
operation.pop("security", None)
120+
return operation
121+
122+
def _update_security_for_mcp(self, schema: dict[str, Any]) -> dict[str, Any]:
123+
"""Update security schemes for MCP (Organisation API Key)."""
124+
schema = schema.copy()
125+
schema["components"] = schema.get("components", {}).copy()
126+
schema["components"]["securitySchemes"] = {
127+
"TOKEN_AUTH": {
128+
"type": "apiKey",
129+
"in": "header",
130+
"name": "Authorization",
131+
"description": "Organisation API Key. Format: Api-Key <key>",
132+
},
133+
}
134+
schema["security"] = [{"TOKEN_AUTH": []}]
55135
return schema
56136

57137

api/api/openapi_views.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Any
2+
3+
from drf_spectacular.views import SpectacularJSONAPIView, SpectacularYAMLAPIView
4+
from rest_framework.request import Request
5+
from rest_framework.response import Response
6+
7+
from api.openapi import MCPSchemaGenerator, SchemaGenerator
8+
9+
10+
class _MCPSchemaViewMixin:
11+
"""
12+
Mixin that provides MCP schema generator selection based on ?mcp=true query parameter.
13+
"""
14+
15+
def get_generator_class(self) -> type:
16+
try:
17+
if self.request.query_params["mcp"].lower() == "true": # type: ignore[attr-defined]
18+
return MCPSchemaGenerator
19+
except (AttributeError, KeyError):
20+
pass
21+
return SchemaGenerator
22+
23+
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response:
24+
self.generator_class = self.get_generator_class()
25+
return super().get(request, *args, **kwargs) # type: ignore[misc, no-any-return]
26+
27+
28+
class CustomSpectacularJSONAPIView(_MCPSchemaViewMixin, SpectacularJSONAPIView):
29+
"""
30+
JSON schema view that supports ?mcp=true query parameter for MCP-filtered output.
31+
"""
32+
33+
34+
class CustomSpectacularYAMLAPIView(_MCPSchemaViewMixin, SpectacularYAMLAPIView):
35+
"""
36+
YAML schema view that supports ?mcp=true query parameter for MCP-filtered output.
37+
"""

api/api/urls/v1.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
from django.conf import settings
22
from django.urls import include, path, re_path
3-
from drf_spectacular.views import (
4-
SpectacularJSONAPIView,
5-
SpectacularSwaggerView,
6-
SpectacularYAMLAPIView,
7-
)
3+
from drf_spectacular.views import SpectacularSwaggerView
84
from rest_framework import permissions, routers
95

6+
from api.openapi_views import CustomSpectacularJSONAPIView, CustomSpectacularYAMLAPIView
107
from app_analytics.views import SDKAnalyticsFlags, SelfHostedTelemetryAPIView
118
from environments.identities.traits.views import SDKTraits
129
from environments.identities.views import SDKIdentities
@@ -73,14 +70,14 @@
7370
# API documentation
7471
path(
7572
"swagger.json",
76-
SpectacularJSONAPIView.as_view(
73+
CustomSpectacularJSONAPIView.as_view(
7774
permission_classes=[schema_view_permission_class],
7875
),
7976
name="schema-json",
8077
),
8178
path(
8279
"swagger.yaml",
83-
SpectacularYAMLAPIView.as_view(
80+
CustomSpectacularYAMLAPIView.as_view(
8481
permission_classes=[schema_view_permission_class],
8582
),
8683
name="schema-yaml",

0 commit comments

Comments
 (0)