Skip to content

Conversation

@Zaimwa9
Copy link
Contributor

@Zaimwa9 Zaimwa9 commented Jan 8, 2026

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Contributes to #6488

  • Added x-gram extensions to tag and describe API endpoints for MCP OpenAPI specs
  • Tagged endpoints with "mcp" tag to include them in the filtered MCP schema
  • Created MCPSchemaGenerator that filters to only MCP-tagged operations
  • Exposed /api/v1/mcp-schema/ endpoint serving the filtered OpenAPI spec
  • Security scheme uses Organisation API Key (Api-Key format)
  • Added makefile command generate-mcp-spec to generate it locally
  • Added CI step to push the MCP specs directly to gram (3 new secrets GRAM_PROJECT, GRAM_ORG, GRAM_API_KEY

Tag a new endpoint

  1. Add tags=["mcp"] to the @extend_schema decorator
  2. Add x-gram extension with name and description:
  @extend_schema(
      tags=["mcp"],
      extensions={
          "x-gram": {
              "name": "list_organisation_projects",
              "description": "Retrieves all the project of a given organisation",
          },
      },
  )
  3. Go to gram and add the tool to the toolsets (MCP)

How did you test this code?

  • Compared current schema generated with existing tools in gram
  • Fully configured Gram on those specs => running with them as we speak
  • Added tests

@Zaimwa9 Zaimwa9 requested review from a team as code owners January 8, 2026 16:54
@Zaimwa9 Zaimwa9 requested review from khvn26 and removed request for a team January 8, 2026 16:54
@vercel
Copy link

vercel bot commented Jan 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Review Updated (UTC)
docs Ignored Ignored Preview Jan 20, 2026 9:43am
flagsmith-frontend-preview Ignored Ignored Preview Jan 20, 2026 9:43am
flagsmith-frontend-staging Ignored Ignored Preview Jan 20, 2026 9:43am

Request Review

@Zaimwa9 Zaimwa9 removed the request for review from khvn26 January 8, 2026 16:54
@github-actions github-actions bot added the api Issue related to the REST API label Jan 8, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-6499 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-6499 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-6499 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-6499 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-6499 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-6499 Finished ✅ Results

…m:Flagsmith/flagsmith into feat/synchronize-openapi-schema-with-gram
@Zaimwa9
Copy link
Contributor Author

Zaimwa9 commented Jan 9, 2026

Adding a push to gram CI step - testing it with platform-pull-request

Zaimwa9 and others added 3 commits January 9, 2026 10:59
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@Zaimwa9 Zaimwa9 changed the title [WIP - not draft] feat: synchronize-openapi-schema-with-gram feat: synchronize-openapi-schema-with-gram Jan 16, 2026
@github-actions github-actions bot added the feature New feature or request label Jan 19, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 19, 2026
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 19, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 19, 2026
@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.10%. Comparing base (04df8d5) to head (f2594e4).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #6499    +/-   ##
========================================
  Coverage   98.09%   98.10%            
========================================
  Files        1293     1295     +2     
  Lines       46607    46811   +204     
========================================
+ Hits        45719    45923   +204     
  Misses        888      888            

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 19, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 19, 2026
@Zaimwa9 Zaimwa9 requested a review from gagantrivedi January 19, 2026 19:49
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 19, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 20, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Jan 20, 2026
@Zaimwa9 Zaimwa9 requested a review from khvn26 January 20, 2026 13:52
Copy link
Contributor

@emyller emyller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few questions, and requests. This is good work so far.

Comment on lines +44 to +45
make install-packages opts="--with saml,auth-controller,workflows,release-pipelines"
make install-private-modules
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this could cause unwanted exposure of our private modules somehow. Can you please confirm what is extracted from private modules and exposed in the MCP server?

Comment on lines +57 to +58
GRAM_ORG: ${{ secrets.GRAM_ORG }}
GRAM_PROJECT: ${{ secrets.GRAM_PROJECT }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these really sensitive?

if self.MCP_TAG in tags:
filtered_operations[method] = self._transform_for_mcp(operation)

if any(isinstance(op, dict) for op in filtered_operations.values()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if any(isinstance(op, dict) for op in filtered_operations.values()):
if has_any_mcp_tag:

I think this would improve with a bool properly managed in the logic above.

Comment on lines +16 to +21
if (
getattr(self, "request", None)
and self.request.query_params.get("mcp", "").lower() == "true"
):
return MCPSchemaGenerator
return SchemaGenerator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (
getattr(self, "request", None)
and self.request.query_params.get("mcp", "").lower() == "true"
):
return MCPSchemaGenerator
return SchemaGenerator
try:
if self.request.query_params["mcp"].lower() == "true":
return MCPSchemaGenerator
except (AttributeError, KeyError,):
pass
return SchemaGenerator

Python suggests EAFP over LBYL. I personally find it easier to read in EAFP.

Comment on lines +33 to +43
def get_generator_class(self) -> type:
if (
getattr(self, "request", None)
and self.request.query_params.get("mcp", "").lower() == "true"
):
return MCPSchemaGenerator
return SchemaGenerator

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response:
self.generator_class = self.get_generator_class()
return super().get(request, *args, **kwargs) # type: ignore[no-untyped-call, no-any-return]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please remove code duplication and move these methods to a base private class?

Comment on lines +177 to +213
def test_custom_json_view__returns_mcp_generator_when_mcp_param_is_true() -> None:
# Given
view = CustomSpectacularJSONAPIView()
view.request = MagicMock()
view.request.query_params = {"mcp": "true"}

# When
generator_class = view.get_generator_class()

# Then
assert generator_class is MCPSchemaGenerator


def test_custom_json_view__returns_schema_generator_when_mcp_param_is_false() -> None:
# Given
view = CustomSpectacularJSONAPIView()
view.request = MagicMock()
view.request.query_params = {"mcp": "false"}

# When
generator_class = view.get_generator_class()

# Then
assert generator_class is SchemaGenerator


def test_custom_json_view__returns_schema_generator_when_no_mcp_param() -> None:
# Given
view = CustomSpectacularJSONAPIView()
view.request = MagicMock()
view.request.query_params = {}

# When
generator_class = view.get_generator_class()

# Then
assert generator_class is SchemaGenerator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can improve with @pytest.mark.parametrize.

Comment on lines +216 to +252
def test_custom_yaml_view__returns_mcp_generator_when_mcp_param_is_true() -> None:
# Given
view = CustomSpectacularYAMLAPIView()
view.request = MagicMock()
view.request.query_params = {"mcp": "true"}

# When
generator_class = view.get_generator_class()

# Then
assert generator_class is MCPSchemaGenerator


def test_custom_yaml_view__returns_schema_generator_when_no_mcp_param() -> None:
# Given
view = CustomSpectacularYAMLAPIView()
view.request = MagicMock()
view.request.query_params = {}

# When
generator_class = view.get_generator_class()

# Then
assert generator_class is SchemaGenerator


def test_custom_json_view__case_insensitive_mcp_param() -> None:
# Given
view = CustomSpectacularJSONAPIView()
view.request = MagicMock()
view.request.query_params = {"mcp": "TRUE"}

# When
generator_class = view.get_generator_class()

# Then
assert generator_class is MCPSchemaGenerator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can improve with @pytest.mark.parametrize.

Comment on lines +255 to +296
def test_mcp_schema__includes_organisations_endpoint() -> None:
# Given
generator = MCPSchemaGenerator()

# When
schema = generator.get_schema(request=None, public=True)

# Then
assert "/api/v1/organisations/" in schema["paths"]
org_list = schema["paths"]["/api/v1/organisations/"]["get"]
assert org_list["x-gram"] == {
"name": "list_organizations",
"description": "Lists all organizations accessible with the provided user API key.",
}


def test_mcp_schema__includes_organisation_projects_endpoint() -> None:
# Given
generator = MCPSchemaGenerator()

# When
schema = generator.get_schema(request=None, public=True)

# Then
assert "/api/v1/organisations/{id}/projects/" in schema["paths"]
projects_list = schema["paths"]["/api/v1/organisations/{id}/projects/"]["get"]
assert projects_list["x-gram"] == {
"name": "list_projects_in_organization",
"description": "Retrieves all projects within a specified organization.",
}


def test_mcp_schema__excludes_non_mcp_endpoints() -> None:
# Given
generator = MCPSchemaGenerator()

# When
schema = generator.get_schema(request=None, public=True)

# Then
# Users endpoint should not be in MCP schema (not tagged)
assert "/api/v1/users/" not in schema["paths"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this could be one test having assert schema["paths"] == ....

@@ -0,0 +1,12 @@
{
"schema_version": "1.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure we're actually tagging 1.0.0 in this product, or perhaps start with 0.0.x?

Comment on lines +33 to +34
git clone https://github.com/flagsmith/flagsmith-saml --depth 1 --branch ${SAML_REVISION} && rm -rf $(SITE_PACKAGES_DIR)/saml && mv ./flagsmith-saml/saml $(SITE_PACKAGES_DIR)
git clone https://github.com/flagsmith/flagsmith-rbac --depth 1 --branch ${RBAC_REVISION} && rm -rf $(SITE_PACKAGES_DIR)/rbac && mv ./flagsmith-rbac/rbac $(SITE_PACKAGES_DIR)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the use case? Maybe this helped you locally?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API feature New feature or request front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants