From 7bc8343c18cf748b58cd5bff1342c3c84c055f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Fri, 1 May 2026 14:59:33 +0400 Subject: [PATCH] Add MCP tools for cookie banner, category, pattern, version, and consent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire cookiebanner.Service into the MCP resolver and expose 24 tools covering full CRUD, activation, versioning, translations, and consent record queries with pagination and filtering support. Signed-off-by: Émile Ré --- .../overview/CookieBannersOverviewPage.tsx | 2 +- pkg/server/api/api.go | 1 + pkg/server/api/mcp/v1/resolver.go | 2 + pkg/server/api/mcp/v1/schema.resolvers.go | 335 ++++++ pkg/server/api/mcp/v1/specification.yaml | 1060 +++++++++++++++++ pkg/server/api/mcp/v1/types/cookie_banner.go | 67 ++ .../api/mcp/v1/types/cookie_banner_version.go | 49 + .../api/mcp/v1/types/cookie_category.go | 61 + .../api/mcp/v1/types/cookie_consent_record.go | 58 + pkg/server/api/mcp/v1/types/cookie_pattern.go | 55 + pkg/server/api/mcp/v1/v1_handler.go | 4 +- 11 files changed, 1692 insertions(+), 2 deletions(-) create mode 100644 pkg/server/api/mcp/v1/types/cookie_banner.go create mode 100644 pkg/server/api/mcp/v1/types/cookie_banner_version.go create mode 100644 pkg/server/api/mcp/v1/types/cookie_category.go create mode 100644 pkg/server/api/mcp/v1/types/cookie_consent_record.go create mode 100644 pkg/server/api/mcp/v1/types/cookie_pattern.go diff --git a/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx b/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx index e859ccb1e..1a1134372 100644 --- a/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx +++ b/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx @@ -174,7 +174,7 @@ export function CookieBannersOverviewPage({ queryRef }: CookieBannersOverviewPag - + {banners.map(banner => ( . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" +) + +func NewCookieBanner(b *coredata.CookieBanner) *CookieBanner { + return &CookieBanner{ + ID: b.ID, + OrganizationID: b.OrganizationID, + Name: b.Name, + Origin: b.Origin, + State: CookieBannerState(b.State), + PrivacyPolicyURL: b.PrivacyPolicyURL, + CookiePolicyURL: b.CookiePolicyURL, + ConsentExpiryDays: b.ConsentExpiryDays, + ConsentMode: CookieBannerConsentMode(b.ConsentMode), + ShowBranding: b.ShowBranding, + DefaultLanguage: b.DefaultLanguage, + CreatedAt: b.CreatedAt, + UpdatedAt: b.UpdatedAt, + } +} + +func NewListCookieBannersOutput(p *page.Page[*coredata.CookieBanner, coredata.CookieBannerOrderField]) ListCookieBannersOutput { + banners := make([]*CookieBanner, 0, len(p.Data)) + for _, b := range p.Data { + banners = append(banners, NewCookieBanner(b)) + } + + var nextCursor *page.CursorKey + if len(p.Data) > 0 { + cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field) + nextCursor = &cursorKey + } + + return ListCookieBannersOutput{ + NextCursor: nextCursor, + CookieBanners: banners, + } +} + +func NewCookieBannerTranslation(t *coredata.CookieBannerTranslation) *CookieBannerTranslation { + return &CookieBannerTranslation{ + ID: t.ID, + CookieBannerID: t.CookieBannerID, + Language: t.Language, + Translations: string(t.Translations), + CreatedAt: t.CreatedAt, + UpdatedAt: t.UpdatedAt, + } +} diff --git a/pkg/server/api/mcp/v1/types/cookie_banner_version.go b/pkg/server/api/mcp/v1/types/cookie_banner_version.go new file mode 100644 index 000000000..c7f953652 --- /dev/null +++ b/pkg/server/api/mcp/v1/types/cookie_banner_version.go @@ -0,0 +1,49 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" +) + +func NewCookieBannerVersion(v *coredata.CookieBannerVersion) *CookieBannerVersion { + return &CookieBannerVersion{ + ID: v.ID, + CookieBannerID: v.CookieBannerID, + Version: v.Version, + State: CookieBannerVersionState(v.State), + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + } +} + +func NewListCookieBannerVersionsOutput(p *page.Page[*coredata.CookieBannerVersion, coredata.CookieBannerVersionOrderField]) ListCookieBannerVersionsOutput { + versions := make([]*CookieBannerVersion, 0, len(p.Data)) + for _, v := range p.Data { + versions = append(versions, NewCookieBannerVersion(v)) + } + + var nextCursor *page.CursorKey + if len(p.Data) > 0 { + cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field) + nextCursor = &cursorKey + } + + return ListCookieBannerVersionsOutput{ + NextCursor: nextCursor, + CookieBannerVersions: versions, + } +} diff --git a/pkg/server/api/mcp/v1/types/cookie_category.go b/pkg/server/api/mcp/v1/types/cookie_category.go new file mode 100644 index 000000000..4ae290d5e --- /dev/null +++ b/pkg/server/api/mcp/v1/types/cookie_category.go @@ -0,0 +1,61 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" +) + +func NewCookieCategory(c *coredata.CookieCategory) *CookieCategory { + var posthogConsent *bool + if c.PostHogConsent { + v := true + posthogConsent = &v + } + + return &CookieCategory{ + ID: c.ID, + OrganizationID: c.OrganizationID, + CookieBannerID: c.CookieBannerID, + Name: c.Name, + Slug: c.Slug, + Description: c.Description, + Kind: CookieCategoryKind(c.Kind), + Rank: c.Rank, + GcmConsentTypes: c.GCMConsentTypes, + PosthogConsent: posthogConsent, + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + } +} + +func NewListCookieCategoriesOutput(p *page.Page[*coredata.CookieCategory, coredata.CookieCategoryOrderField]) ListCookieCategoriesOutput { + categories := make([]*CookieCategory, 0, len(p.Data)) + for _, c := range p.Data { + categories = append(categories, NewCookieCategory(c)) + } + + var nextCursor *page.CursorKey + if len(p.Data) > 0 { + cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field) + nextCursor = &cursorKey + } + + return ListCookieCategoriesOutput{ + NextCursor: nextCursor, + CookieCategories: categories, + } +} diff --git a/pkg/server/api/mcp/v1/types/cookie_consent_record.go b/pkg/server/api/mcp/v1/types/cookie_consent_record.go new file mode 100644 index 000000000..e914fdcb9 --- /dev/null +++ b/pkg/server/api/mcp/v1/types/cookie_consent_record.go @@ -0,0 +1,58 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" +) + +func NewCookieConsentRecord(r *coredata.CookieConsentRecord) *CookieConsentRecord { + var consentData string + if r.ConsentData != nil { + consentData = string(r.ConsentData) + } + + return &CookieConsentRecord{ + ID: r.ID, + CookieBannerID: r.CookieBannerID, + CookieBannerVersionID: r.CookieBannerVersionID, + VisitorID: r.VisitorID, + IPAddress: r.IPAddress, + UserAgent: r.UserAgent, + ConsentData: consentData, + Action: CookieConsentRecordAction(r.Action), + SdkVersion: r.SdkVersion, + CreatedAt: r.CreatedAt, + } +} + +func NewListCookieConsentRecordsOutput(p *page.Page[*coredata.CookieConsentRecord, coredata.CookieConsentRecordOrderField]) ListCookieConsentRecordsOutput { + records := make([]*CookieConsentRecord, 0, len(p.Data)) + for _, r := range p.Data { + records = append(records, NewCookieConsentRecord(r)) + } + + var nextCursor *page.CursorKey + if len(p.Data) > 0 { + cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field) + nextCursor = &cursorKey + } + + return ListCookieConsentRecordsOutput{ + NextCursor: nextCursor, + CookieConsentRecords: records, + } +} diff --git a/pkg/server/api/mcp/v1/types/cookie_pattern.go b/pkg/server/api/mcp/v1/types/cookie_pattern.go new file mode 100644 index 000000000..727709654 --- /dev/null +++ b/pkg/server/api/mcp/v1/types/cookie_pattern.go @@ -0,0 +1,55 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" +) + +func NewCookiePattern(p *coredata.CookiePattern) *CookiePattern { + return &CookiePattern{ + ID: p.ID, + OrganizationID: p.OrganizationID, + CookieBannerID: p.CookieBannerID, + CookieCategoryID: p.CookieCategoryID, + Pattern: p.Pattern, + MatchType: CookiePatternMatchType(p.MatchType), + DisplayName: p.DisplayName, + MaxAgeSeconds: p.MaxAgeSeconds, + Description: p.Description, + Source: CookiePatternSource(p.Source), + CreatedAt: p.CreatedAt, + UpdatedAt: p.UpdatedAt, + } +} + +func NewListCookiePatternsOutput(pg *page.Page[*coredata.CookiePattern, coredata.CookiePatternOrderField]) ListCookiePatternsOutput { + patterns := make([]*CookiePattern, 0, len(pg.Data)) + for _, p := range pg.Data { + patterns = append(patterns, NewCookiePattern(p)) + } + + var nextCursor *page.CursorKey + if len(pg.Data) > 0 { + cursorKey := pg.Data[len(pg.Data)-1].CursorKey(pg.Cursor.OrderBy.Field) + nextCursor = &cursorKey + } + + return ListCookiePatternsOutput{ + NextCursor: nextCursor, + CookiePatterns: patterns, + } +} diff --git a/pkg/server/api/mcp/v1/v1_handler.go b/pkg/server/api/mcp/v1/v1_handler.go index 2d2678d08..fe96e9ff9 100644 --- a/pkg/server/api/mcp/v1/v1_handler.go +++ b/pkg/server/api/mcp/v1/v1_handler.go @@ -23,6 +23,7 @@ import ( "go.gearno.de/kit/log" mcpgenmcp "go.probo.inc/mcpgen/mcp" "go.probo.inc/probo/pkg/accessreview" + "go.probo.inc/probo/pkg/cookiebanner" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/probo" @@ -35,7 +36,7 @@ func (r *Resolver) ProboService(ctx context.Context, objectID gid.GID) *probo.Te return r.proboSvc.WithTenant(objectID.TenantID()) } -func NewMux(logger *log.Logger, proboSvc *probo.Service, iamSvc *iam.Service, accessReviewSvc *accessreview.Service, tokenSecret string) *chi.Mux { +func NewMux(logger *log.Logger, proboSvc *probo.Service, iamSvc *iam.Service, accessReviewSvc *accessreview.Service, cookieBannerSvc *cookiebanner.Service, tokenSecret string) *chi.Mux { logger = logger.Named("mcp.v1") logger.Info("initializing MCP server") @@ -45,6 +46,7 @@ func NewMux(logger *log.Logger, proboSvc *probo.Service, iamSvc *iam.Service, ac proboSvc: proboSvc, iamSvc: iamSvc, accessReview: accessReviewSvc, + cookieBanner: cookieBannerSvc, logger: logger, }