Add MCP tools for cookie banner, category, pattern, version, and consent

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é <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-05-01 14:59:33 +04:00
parent c43f61d109
commit 7bc8343c18
11 changed files with 1692 additions and 2 deletions

View File

@@ -174,7 +174,7 @@ export function CookieBannersOverviewPage({ queryRef }: CookieBannersOverviewPag
<Button to={newBannerHref}>{__("Create Banner")}</Button>
</div>
<Card className="divide-y divide-border rounded-lg border">
<Card className="divide-y divide-border-low rounded-lg">
{banners.map(banner => (
<Link
key={banner.id}

View File

@@ -202,6 +202,7 @@ func NewServer(cfg Config) (*Server, error) {
cfg.Probo,
cfg.IAM,
cfg.AccessReview,
cfg.CookieBanner,
cfg.TokenSecret,
),
slackHandler: slack_v1.NewMux(

View File

@@ -23,6 +23,7 @@ import (
"go.gearno.de/kit/log"
"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"
@@ -34,6 +35,7 @@ type Resolver struct {
proboSvc *probo.Service
iamSvc *iam.Service
accessReview *accessreview.Service
cookieBanner *cookiebanner.Service
logger *log.Logger
}

View File

@@ -6,12 +6,14 @@ package mcp_v1
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
"go.probo.inc/probo/pkg/accessreview"
"go.probo.inc/probo/pkg/cookiebanner"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam"
@@ -4852,3 +4854,336 @@ func (r *Resolver) PublishVendorListTool(ctx context.Context, req *mcp.CallToolR
DocumentVersionID: documentVersion.ID,
}, nil
}
func (r *Resolver) ListCookieBannersTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCookieBannersInput) (*mcp.CallToolResult, types.ListCookieBannersOutput, error) {
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionCookieBannerList)
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.CookieBannerOrderField]{Field: coredata.CookieBannerOrderFieldCreatedAt, Direction: page.OrderDirectionDesc})
banners, err := r.cookieBanner.ListCookieBannersForOrganization(ctx, scope, input.OrganizationID, cursor, coredata.NewCookieBannerFilter(nil))
if err != nil {
panic(fmt.Errorf("cannot list cookie banners: %w", err))
}
p := page.NewPage(banners, cursor)
return nil, types.NewListCookieBannersOutput(p), nil
}
func (r *Resolver) GetCookieBannerTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetCookieBannerInput) (*mcp.CallToolResult, types.GetCookieBannerOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieBannerGet)
scope := coredata.NewScopeFromObjectID(input.ID)
banner, err := r.cookieBanner.GetCookieBanner(ctx, scope, input.ID)
if err != nil {
return nil, types.GetCookieBannerOutput{}, fmt.Errorf("cannot get cookie banner: %w", err)
}
return nil, types.GetCookieBannerOutput{CookieBanner: types.NewCookieBanner(banner)}, nil
}
func (r *Resolver) AddCookieBannerTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCookieBannerInput) (*mcp.CallToolResult, types.AddCookieBannerOutput, error) {
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionCookieBannerCreate)
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
banner, err := r.cookieBanner.CreateCookieBanner(ctx, scope, cookiebanner.CreateCookieBannerRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Origin: input.Origin,
PrivacyPolicyURL: input.PrivacyPolicyURL,
CookiePolicyURL: input.CookiePolicyURL,
ConsentExpiryDays: input.ConsentExpiryDays,
ConsentMode: coredata.CookieConsentMode(input.ConsentMode),
})
if err != nil {
return nil, types.AddCookieBannerOutput{}, fmt.Errorf("cannot create cookie banner: %w", err)
}
return nil, types.AddCookieBannerOutput{CookieBanner: types.NewCookieBanner(banner)}, nil
}
func (r *Resolver) UpdateCookieBannerTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCookieBannerInput) (*mcp.CallToolResult, types.UpdateCookieBannerOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieBannerUpdate)
scope := coredata.NewScopeFromObjectID(input.ID)
updateReq := cookiebanner.UpdateCookieBannerRequest{CookieBannerID: input.ID}
if v := UnwrapOmittable(input.Name); v != nil && *v != nil {
updateReq.Name = *v
}
if v := UnwrapOmittable(input.PrivacyPolicyURL); v != nil && *v != nil {
updateReq.PrivacyPolicyURL = *v
}
if v := UnwrapOmittable(input.CookiePolicyURL); v != nil && *v != nil {
updateReq.CookiePolicyURL = *v
}
if v := UnwrapOmittable(input.ConsentExpiryDays); v != nil && *v != nil {
updateReq.ConsentExpiryDays = *v
}
if v := UnwrapOmittable(input.ConsentMode); v != nil && *v != nil {
mode := coredata.CookieConsentMode(**v)
updateReq.ConsentMode = &mode
}
if v := UnwrapOmittable(input.DefaultLanguage); v != nil && *v != nil {
updateReq.DefaultLanguage = *v
}
banner, err := r.cookieBanner.UpdateCookieBanner(ctx, scope, updateReq)
if err != nil {
return nil, types.UpdateCookieBannerOutput{}, fmt.Errorf("cannot update cookie banner: %w", err)
}
return nil, types.UpdateCookieBannerOutput{CookieBanner: types.NewCookieBanner(banner)}, nil
}
func (r *Resolver) DeleteCookieBannerTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCookieBannerInput) (*mcp.CallToolResult, types.DeleteCookieBannerOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieBannerDelete)
scope := coredata.NewScopeFromObjectID(input.ID)
if err := r.cookieBanner.DeleteCookieBanner(ctx, scope, input.ID); err != nil {
return nil, types.DeleteCookieBannerOutput{}, fmt.Errorf("cannot delete cookie banner: %w", err)
}
return nil, types.DeleteCookieBannerOutput{DeletedID: input.ID}, nil
}
func (r *Resolver) ActivateCookieBannerTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ActivateCookieBannerInput) (*mcp.CallToolResult, types.ActivateCookieBannerOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieBannerActivate)
scope := coredata.NewScopeFromObjectID(input.ID)
banner, err := r.cookieBanner.ActivateCookieBanner(ctx, scope, input.ID)
if err != nil {
return nil, types.ActivateCookieBannerOutput{}, fmt.Errorf("cannot activate cookie banner: %w", err)
}
return nil, types.ActivateCookieBannerOutput{CookieBanner: types.NewCookieBanner(banner)}, nil
}
func (r *Resolver) DeactivateCookieBannerTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeactivateCookieBannerInput) (*mcp.CallToolResult, types.DeactivateCookieBannerOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieBannerDeactivate)
scope := coredata.NewScopeFromObjectID(input.ID)
banner, err := r.cookieBanner.DeactivateCookieBanner(ctx, scope, input.ID)
if err != nil {
return nil, types.DeactivateCookieBannerOutput{}, fmt.Errorf("cannot deactivate cookie banner: %w", err)
}
return nil, types.DeactivateCookieBannerOutput{CookieBanner: types.NewCookieBanner(banner)}, nil
}
func (r *Resolver) ListCookieCategoriesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCookieCategoriesInput) (*mcp.CallToolResult, types.ListCookieCategoriesOutput, error) {
r.MustAuthorize(ctx, input.CookieBannerID, probo.ActionCookieCategoryList)
scope := coredata.NewScopeFromObjectID(input.CookieBannerID)
cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.CookieCategoryOrderField]{Field: coredata.CookieCategoryOrderFieldRank, Direction: page.OrderDirectionAsc})
categories, err := r.cookieBanner.ListCookieCategoriesForBanner(ctx, scope, input.CookieBannerID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list cookie categories: %w", err))
}
p := page.NewPage(categories, cursor)
return nil, types.NewListCookieCategoriesOutput(p), nil
}
func (r *Resolver) GetCookieCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetCookieCategoryInput) (*mcp.CallToolResult, types.GetCookieCategoryOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieCategoryGet)
scope := coredata.NewScopeFromObjectID(input.ID)
category, err := r.cookieBanner.GetCookieCategory(ctx, scope, input.ID)
if err != nil {
return nil, types.GetCookieCategoryOutput{}, fmt.Errorf("cannot get cookie category: %w", err)
}
return nil, types.GetCookieCategoryOutput{CookieCategory: types.NewCookieCategory(category)}, nil
}
func (r *Resolver) AddCookieCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCookieCategoryInput) (*mcp.CallToolResult, types.AddCookieCategoryOutput, error) {
r.MustAuthorize(ctx, input.CookieBannerID, probo.ActionCookieCategoryCreate)
scope := coredata.NewScopeFromObjectID(input.CookieBannerID)
category, err := r.cookieBanner.CreateCookieCategory(ctx, scope, cookiebanner.CreateCookieCategoryRequest{
CookieBannerID: input.CookieBannerID,
Name: input.Name,
Slug: input.Slug,
Description: input.Description,
Rank: input.Rank,
})
if err != nil {
return nil, types.AddCookieCategoryOutput{}, fmt.Errorf("cannot create cookie category: %w", err)
}
return nil, types.AddCookieCategoryOutput{CookieCategory: types.NewCookieCategory(category)}, nil
}
func (r *Resolver) UpdateCookieCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCookieCategoryInput) (*mcp.CallToolResult, types.UpdateCookieCategoryOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieCategoryUpdate)
scope := coredata.NewScopeFromObjectID(input.ID)
updateReq := cookiebanner.UpdateCookieCategoryRequest{CookieCategoryID: input.ID}
if v := UnwrapOmittable(input.Name); v != nil && *v != nil {
updateReq.Name = *v
}
if v := UnwrapOmittable(input.Slug); v != nil && *v != nil {
updateReq.Slug = *v
}
if v := UnwrapOmittable(input.Description); v != nil && *v != nil {
updateReq.Description = *v
}
category, err := r.cookieBanner.UpdateCookieCategory(ctx, scope, updateReq)
if err != nil {
return nil, types.UpdateCookieCategoryOutput{}, fmt.Errorf("cannot update cookie category: %w", err)
}
return nil, types.UpdateCookieCategoryOutput{CookieCategory: types.NewCookieCategory(category)}, nil
}
func (r *Resolver) DeleteCookieCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCookieCategoryInput) (*mcp.CallToolResult, types.DeleteCookieCategoryOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieCategoryDelete)
scope := coredata.NewScopeFromObjectID(input.ID)
if err := r.cookieBanner.DeleteCookieCategory(ctx, scope, input.ID); err != nil {
return nil, types.DeleteCookieCategoryOutput{}, fmt.Errorf("cannot delete cookie category: %w", err)
}
return nil, types.DeleteCookieCategoryOutput{DeletedID: input.ID}, nil
}
func (r *Resolver) ReorderCookieCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ReorderCookieCategoryInput) (*mcp.CallToolResult, types.ReorderCookieCategoryOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieCategoryUpdate)
scope := coredata.NewScopeFromObjectID(input.ID)
_, err := r.cookieBanner.ReorderCookieCategory(ctx, scope, cookiebanner.ReorderCookieCategoryRequest{
CookieCategoryID: input.ID,
Rank: input.Rank,
})
if err != nil {
return nil, types.ReorderCookieCategoryOutput{}, fmt.Errorf("cannot reorder cookie category: %w", err)
}
category, err := r.cookieBanner.GetCookieCategory(ctx, scope, input.ID)
if err != nil {
return nil, types.ReorderCookieCategoryOutput{}, fmt.Errorf("cannot get cookie category: %w", err)
}
return nil, types.ReorderCookieCategoryOutput{CookieCategory: types.NewCookieCategory(category)}, nil
}
func (r *Resolver) ListCookiePatternsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCookiePatternsInput) (*mcp.CallToolResult, types.ListCookiePatternsOutput, error) {
r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionCookiePatternList)
scope := coredata.NewScopeFromObjectID(input.CookieCategoryID)
cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.CookiePatternOrderField]{Field: coredata.CookiePatternOrderFieldCreatedAt, Direction: page.OrderDirectionAsc})
patterns, err := r.cookieBanner.ListCookiePatternsForCategory(ctx, scope, input.CookieCategoryID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list cookie patterns: %w", err))
}
p := page.NewPage(patterns, cursor)
return nil, types.NewListCookiePatternsOutput(p), nil
}
func (r *Resolver) GetCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetCookiePatternInput) (*mcp.CallToolResult, types.GetCookiePatternOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookiePatternGet)
scope := coredata.NewScopeFromObjectID(input.ID)
pattern, err := r.cookieBanner.GetCookiePattern(ctx, scope, input.ID)
if err != nil {
return nil, types.GetCookiePatternOutput{}, fmt.Errorf("cannot get cookie pattern: %w", err)
}
return nil, types.GetCookiePatternOutput{CookiePattern: types.NewCookiePattern(pattern)}, nil
}
func (r *Resolver) AddCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCookiePatternInput) (*mcp.CallToolResult, types.AddCookiePatternOutput, error) {
r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionCookiePatternCreate)
scope := coredata.NewScopeFromObjectID(input.CookieCategoryID)
pattern, err := r.cookieBanner.CreateCookiePattern(ctx, scope, cookiebanner.CreateCookiePatternRequest{
CookieCategoryID: input.CookieCategoryID,
Pattern: input.Pattern,
MatchType: coredata.CookiePatternMatchType(input.MatchType),
DisplayName: input.DisplayName,
MaxAgeSeconds: input.MaxAgeSeconds,
Description: input.Description,
})
if err != nil {
return nil, types.AddCookiePatternOutput{}, fmt.Errorf("cannot create cookie pattern: %w", err)
}
return nil, types.AddCookiePatternOutput{CookiePattern: types.NewCookiePattern(pattern)}, nil
}
func (r *Resolver) UpdateCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCookiePatternInput) (*mcp.CallToolResult, types.UpdateCookiePatternOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookiePatternUpdate)
scope := coredata.NewScopeFromObjectID(input.ID)
updateReq := cookiebanner.UpdateCookiePatternRequest{CookiePatternID: input.ID}
if v := UnwrapOmittable(input.DisplayName); v != nil && *v != nil {
updateReq.DisplayName = *v
}
if input.MaxAgeSeconds.IsSet() {
val, _ := input.MaxAgeSeconds.Value()
updateReq.MaxAgeSeconds = &val
}
if v := UnwrapOmittable(input.Description); v != nil && *v != nil {
updateReq.Description = *v
}
pattern, err := r.cookieBanner.UpdateCookiePattern(ctx, scope, updateReq)
if err != nil {
return nil, types.UpdateCookiePatternOutput{}, fmt.Errorf("cannot update cookie pattern: %w", err)
}
return nil, types.UpdateCookiePatternOutput{CookiePattern: types.NewCookiePattern(pattern)}, nil
}
func (r *Resolver) DeleteCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCookiePatternInput) (*mcp.CallToolResult, types.DeleteCookiePatternOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookiePatternDelete)
scope := coredata.NewScopeFromObjectID(input.ID)
if err := r.cookieBanner.DeleteCookiePattern(ctx, scope, input.ID); err != nil {
return nil, types.DeleteCookiePatternOutput{}, fmt.Errorf("cannot delete cookie pattern: %w", err)
}
return nil, types.DeleteCookiePatternOutput{DeletedID: input.ID}, nil
}
func (r *Resolver) MoveCookiePatternToCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.MoveCookiePatternToCategoryInput) (*mcp.CallToolResult, types.MoveCookiePatternToCategoryOutput, error) {
r.MustAuthorize(ctx, input.CookiePatternID, probo.ActionCookiePatternUpdate)
scope := coredata.NewScopeFromObjectID(input.CookiePatternID)
result, err := r.cookieBanner.MoveCookiePatternToCategory(ctx, scope, cookiebanner.MoveCookiePatternToCategoryRequest{
CookiePatternID: input.CookiePatternID,
TargetCookieCategoryID: input.TargetCookieCategoryID,
})
if err != nil {
return nil, types.MoveCookiePatternToCategoryOutput{}, fmt.Errorf("cannot move cookie pattern: %w", err)
}
return nil, types.MoveCookiePatternToCategoryOutput{CookiePattern: types.NewCookiePattern(result.CookiePattern)}, nil
}
func (r *Resolver) PublishCookieBannerVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishCookieBannerVersionInput) (*mcp.CallToolResult, types.PublishCookieBannerVersionOutput, error) {
r.MustAuthorize(ctx, input.CookieBannerID, probo.ActionCookieBannerVersionPublish)
scope := coredata.NewScopeFromObjectID(input.CookieBannerID)
version, err := r.cookieBanner.PublishCookieBannerVersion(ctx, scope, input.CookieBannerID)
if err != nil {
return nil, types.PublishCookieBannerVersionOutput{}, fmt.Errorf("cannot publish cookie banner version: %w", err)
}
return nil, types.PublishCookieBannerVersionOutput{CookieBannerVersion: types.NewCookieBannerVersion(version)}, nil
}
func (r *Resolver) ListCookieBannerVersionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCookieBannerVersionsInput) (*mcp.CallToolResult, types.ListCookieBannerVersionsOutput, error) {
r.MustAuthorize(ctx, input.CookieBannerID, probo.ActionCookieBannerVersionList)
scope := coredata.NewScopeFromObjectID(input.CookieBannerID)
cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.CookieBannerVersionOrderField]{Field: coredata.CookieBannerVersionOrderFieldCreatedAt, Direction: page.OrderDirectionDesc})
versions, err := r.cookieBanner.ListCookieBannerVersionsForBanner(ctx, scope, input.CookieBannerID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list cookie banner versions: %w", err))
}
p := page.NewPage(versions, cursor)
return nil, types.NewListCookieBannerVersionsOutput(p), nil
}
func (r *Resolver) UpsertCookieBannerTranslationTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpsertCookieBannerTranslationInput) (*mcp.CallToolResult, types.UpsertCookieBannerTranslationOutput, error) {
r.MustAuthorize(ctx, input.CookieBannerID, probo.ActionCookieBannerUpdate)
scope := coredata.NewScopeFromObjectID(input.CookieBannerID)
translation, err := r.cookieBanner.UpsertCookieBannerTranslation(ctx, scope, cookiebanner.UpsertCookieBannerTranslationRequest{
CookieBannerID: input.CookieBannerID,
Language: input.Language,
Translations: json.RawMessage(input.Translations),
})
if err != nil {
return nil, types.UpsertCookieBannerTranslationOutput{}, fmt.Errorf("cannot upsert cookie banner translation: %w", err)
}
return nil, types.UpsertCookieBannerTranslationOutput{CookieBannerTranslation: types.NewCookieBannerTranslation(translation)}, nil
}
func (r *Resolver) ListCookieConsentRecordsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCookieConsentRecordsInput) (*mcp.CallToolResult, types.ListCookieConsentRecordsOutput, error) {
r.MustAuthorize(ctx, input.CookieBannerID, probo.ActionCookieConsentRecordList)
scope := coredata.NewScopeFromObjectID(input.CookieBannerID)
cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.CookieConsentRecordOrderField]{Field: coredata.CookieConsentRecordOrderFieldCreatedAt, Direction: page.OrderDirectionDesc})
var action *coredata.CookieConsentAction
if input.Action != nil {
a := coredata.CookieConsentAction(*input.Action)
action = &a
}
filter := coredata.NewCookieConsentRecordFilter(action, input.VisitorID, input.Version)
records, err := r.cookieBanner.ListCookieConsentRecordsForBanner(ctx, scope, input.CookieBannerID, cursor, filter)
if err != nil {
panic(fmt.Errorf("cannot list cookie consent records: %w", err))
}
p := page.NewPage(records, cursor)
return nil, types.NewListCookieConsentRecordsOutput(p), nil
}
func (r *Resolver) GetCookieConsentRecordTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetCookieConsentRecordInput) (*mcp.CallToolResult, types.GetCookieConsentRecordOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionCookieConsentRecordList)
scope := coredata.NewScopeFromObjectID(input.ID)
record, err := r.cookieBanner.GetCookieConsentRecord(ctx, scope, input.ID)
if err != nil {
return nil, types.GetCookieConsentRecordOutput{}, fmt.Errorf("cannot get cookie consent record: %w", err)
}
return nil, types.GetCookieConsentRecordOutput{CookieConsentRecord: types.NewCookieConsentRecord(record)}, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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,
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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,
}
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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,
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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,
}
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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,
}
}

View File

@@ -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,
}