mcp: add TrackerResource tools and drop SCRIPT/IFRAME from tracker_type

- Remove SCRIPT/IFRAME from the tracker_type enum in the MCP spec.
- Add TrackerResource schema in components/schemas.
- Add 6 tools: listTrackerResources, getTrackerResource,
  addTrackerResource, updateTrackerResource, deleteTrackerResource,
  moveTrackerResourceToCategory with input/output schemas.
- Add types/tracker_resource.go helper and resolver implementations.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-05-08 19:08:04 +04:00
parent 663dc7478f
commit 5bf6a67c06
3 changed files with 393 additions and 2 deletions

View File

@@ -5180,3 +5180,88 @@ func (r *Resolver) PublishDocumentTool(ctx context.Context, req *mcp.CallToolReq
}
return nil, output, nil
}
func (r *Resolver) ListTrackerResourcesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListTrackerResourcesInput) (*mcp.CallToolResult, types.ListTrackerResourcesOutput, error) {
r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionTrackerResourceList)
scope := coredata.NewScopeFromObjectID(input.CookieCategoryID)
cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.TrackerResourceOrderField]{Field: coredata.TrackerResourceOrderFieldCreatedAt, Direction: page.OrderDirectionAsc})
resources, err := r.cookieBanner.ListTrackerResourcesForCategory(ctx, scope, input.CookieCategoryID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list tracker resources: %w", err))
}
p := page.NewPage(resources, cursor)
return nil, types.NewListTrackerResourcesOutput(p), nil
}
func (r *Resolver) GetTrackerResourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetTrackerResourceInput) (*mcp.CallToolResult, types.GetTrackerResourceOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionTrackerResourceGet)
scope := coredata.NewScopeFromObjectID(input.ID)
resource, err := r.cookieBanner.GetTrackerResource(ctx, scope, input.ID)
if err != nil {
return nil, types.GetTrackerResourceOutput{}, fmt.Errorf("cannot get tracker resource: %w", err)
}
return nil, types.GetTrackerResourceOutput{TrackerResource: types.NewTrackerResource(resource)}, nil
}
func (r *Resolver) AddTrackerResourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddTrackerResourceInput) (*mcp.CallToolResult, types.AddTrackerResourceOutput, error) {
r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionTrackerResourceCreate)
scope := coredata.NewScopeFromObjectID(input.CookieCategoryID)
description := ""
if input.Description != nil {
description = *input.Description
}
resource, err := r.cookieBanner.CreateTrackerResource(ctx, scope, cookiebanner.CreateTrackerResourceRequest{
CookieCategoryID: input.CookieCategoryID,
ResourceType: coredata.TrackerResourceType(input.ResourceType),
Origin: input.Origin,
Path: input.Path,
DisplayName: input.DisplayName,
Description: description,
})
if err != nil {
return nil, types.AddTrackerResourceOutput{}, fmt.Errorf("cannot create tracker resource: %w", err)
}
return nil, types.AddTrackerResourceOutput{TrackerResource: types.NewTrackerResource(resource)}, nil
}
func (r *Resolver) UpdateTrackerResourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateTrackerResourceInput) (*mcp.CallToolResult, types.UpdateTrackerResourceOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionTrackerResourceUpdate)
scope := coredata.NewScopeFromObjectID(input.ID)
updateReq := cookiebanner.UpdateTrackerResourceRequest{TrackerResourceID: input.ID}
if v := UnwrapOmittable(input.DisplayName); v != nil && *v != nil {
updateReq.DisplayName = *v
}
if v := UnwrapOmittable(input.Description); v != nil && *v != nil {
updateReq.Description = *v
}
if v := UnwrapOmittable(input.Excluded); v != nil && *v != nil {
updateReq.Excluded = *v
}
resource, err := r.cookieBanner.UpdateTrackerResource(ctx, scope, updateReq)
if err != nil {
return nil, types.UpdateTrackerResourceOutput{}, fmt.Errorf("cannot update tracker resource: %w", err)
}
return nil, types.UpdateTrackerResourceOutput{TrackerResource: types.NewTrackerResource(resource)}, nil
}
func (r *Resolver) DeleteTrackerResourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteTrackerResourceInput) (*mcp.CallToolResult, types.DeleteTrackerResourceOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionTrackerResourceDelete)
scope := coredata.NewScopeFromObjectID(input.ID)
if err := r.cookieBanner.DeleteTrackerResource(ctx, scope, input.ID); err != nil {
return nil, types.DeleteTrackerResourceOutput{}, fmt.Errorf("cannot delete tracker resource: %w", err)
}
return nil, types.DeleteTrackerResourceOutput{DeletedID: input.ID}, nil
}
func (r *Resolver) MoveTrackerResourceToCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.MoveTrackerResourceToCategoryInput) (*mcp.CallToolResult, types.MoveTrackerResourceToCategoryOutput, error) {
r.MustAuthorize(ctx, input.TrackerResourceID, probo.ActionTrackerResourceUpdate)
scope := coredata.NewScopeFromObjectID(input.TrackerResourceID)
result, err := r.cookieBanner.MoveTrackerResourceToCategory(ctx, scope, cookiebanner.MoveTrackerResourceToCategoryRequest{
TrackerResourceID: input.TrackerResourceID,
TargetCookieCategoryID: input.TargetCookieCategoryID,
})
if err != nil {
return nil, types.MoveTrackerResourceToCategoryOutput{}, fmt.Errorf("cannot move tracker resource: %w", err)
}
return nil, types.MoveTrackerResourceToCategoryOutput{TrackerResource: types.NewTrackerResource(result.TrackerResource)}, nil
}

View File

@@ -9450,7 +9450,7 @@ components:
description: Cookie category ID
tracker_type:
type: string
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, INDEXED_DB, SCRIPT, IFRAME]
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, INDEXED_DB]
description: Type of tracker
pattern:
type: string
@@ -9492,6 +9492,68 @@ components:
format: date-time
description: Last update timestamp
TrackerResource:
type: object
required:
- id
- organization_id
- cookie_banner_id
- cookie_category_id
- resource_type
- origin
- path
- display_name
- description
- excluded
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
description: Tracker resource ID
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
cookie_banner_id:
$ref: "#/components/schemas/GID"
description: Cookie banner ID
cookie_category_id:
$ref: "#/components/schemas/GID"
description: Cookie category ID
resource_type:
type: string
enum: [SCRIPT, IFRAME]
description: Type of tracked resource
origin:
type: string
description: Resource origin (scheme://host[:port])
path:
type: string
description: Resource path
display_name:
type: string
description: Human-friendly display name
description:
type: string
description: Resource description
excluded:
type: boolean
description: Whether this resource is excluded from the consent banner
last_detected_at:
type:
- string
- "null"
format: date-time
description: Timestamp when this resource was last detected
created_at:
type: string
format: date-time
description: Creation timestamp
updated_at:
type: string
format: date-time
description: Last update timestamp
CookieBannerVersion:
type: object
required:
@@ -9984,7 +10046,7 @@ components:
$ref: "#/components/schemas/GID"
tracker_type:
type: string
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, INDEXED_DB, SCRIPT, IFRAME]
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, INDEXED_DB]
pattern:
type: string
match_type:
@@ -10071,6 +10133,143 @@ components:
tracker_pattern:
$ref: "#/components/schemas/TrackerPattern"
ListTrackerResourcesInput:
type: object
required:
- cookie_category_id
properties:
cookie_category_id:
$ref: "#/components/schemas/GID"
size:
type: integer
cursor:
$ref: "#/components/schemas/CursorKey"
ListTrackerResourcesOutput:
type: object
required:
- tracker_resources
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
tracker_resources:
type: array
items:
$ref: "#/components/schemas/TrackerResource"
GetTrackerResourceInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
GetTrackerResourceOutput:
type: object
required:
- tracker_resource
properties:
tracker_resource:
$ref: "#/components/schemas/TrackerResource"
AddTrackerResourceInput:
type: object
required:
- cookie_category_id
- resource_type
- origin
- path
- display_name
properties:
cookie_category_id:
$ref: "#/components/schemas/GID"
resource_type:
type: string
enum: [SCRIPT, IFRAME]
origin:
type: string
path:
type: string
display_name:
type: string
description:
type: string
AddTrackerResourceOutput:
type: object
required:
- tracker_resource
properties:
tracker_resource:
$ref: "#/components/schemas/TrackerResource"
UpdateTrackerResourceInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
display_name:
type:
- string
- "null"
go.probo.inc/mcpgen/omittable: true
description:
type:
- string
- "null"
go.probo.inc/mcpgen/omittable: true
excluded:
type:
- boolean
- "null"
go.probo.inc/mcpgen/omittable: true
UpdateTrackerResourceOutput:
type: object
required:
- tracker_resource
properties:
tracker_resource:
$ref: "#/components/schemas/TrackerResource"
DeleteTrackerResourceInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
DeleteTrackerResourceOutput:
type: object
required:
- deleted_id
properties:
deleted_id:
$ref: "#/components/schemas/GID"
MoveTrackerResourceToCategoryInput:
type: object
required:
- tracker_resource_id
- target_cookie_category_id
properties:
tracker_resource_id:
$ref: "#/components/schemas/GID"
target_cookie_category_id:
$ref: "#/components/schemas/GID"
MoveTrackerResourceToCategoryOutput:
type: object
required:
- tracker_resource
properties:
tracker_resource:
$ref: "#/components/schemas/TrackerResource"
PublishCookieBannerVersionInput:
type: object
required:
@@ -12353,6 +12552,57 @@ tools:
$ref: "#/components/schemas/MoveTrackerPatternToCategoryInput"
outputSchema:
$ref: "#/components/schemas/MoveTrackerPatternToCategoryOutput"
- name: listTrackerResources
description: List all tracker resources for a category
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListTrackerResourcesInput"
outputSchema:
$ref: "#/components/schemas/ListTrackerResourcesOutput"
- name: getTrackerResource
description: Get a tracker resource by ID
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/GetTrackerResourceInput"
outputSchema:
$ref: "#/components/schemas/GetTrackerResourceOutput"
- name: addTrackerResource
description: Create a new tracker resource for a category
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/AddTrackerResourceInput"
outputSchema:
$ref: "#/components/schemas/AddTrackerResourceOutput"
- name: updateTrackerResource
description: Update an existing tracker resource
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/UpdateTrackerResourceInput"
outputSchema:
$ref: "#/components/schemas/UpdateTrackerResourceOutput"
- name: deleteTrackerResource
description: Delete a tracker resource
hints:
readonly: false
destructive: true
inputSchema:
$ref: "#/components/schemas/DeleteTrackerResourceInput"
outputSchema:
$ref: "#/components/schemas/DeleteTrackerResourceOutput"
- name: moveTrackerResourceToCategory
description: Move a tracker resource to a different category
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/MoveTrackerResourceToCategoryInput"
outputSchema:
$ref: "#/components/schemas/MoveTrackerResourceToCategoryOutput"
- name: publishCookieBannerVersion
description: Publish the current draft version of a cookie banner
hints:

View File

@@ -0,0 +1,56 @@
// 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 NewTrackerResource(tr *coredata.TrackerResource) *TrackerResource {
return &TrackerResource{
ID: tr.ID,
OrganizationID: tr.OrganizationID,
CookieBannerID: tr.CookieBannerID,
CookieCategoryID: tr.CookieCategoryID,
ResourceType: TrackerResourceResourceType(tr.ResourceType),
Origin: tr.Origin,
Path: tr.Path,
DisplayName: tr.DisplayName,
Description: tr.Description,
Excluded: tr.Excluded,
LastDetectedAt: tr.LastDetectedAt,
CreatedAt: tr.CreatedAt,
UpdatedAt: tr.UpdatedAt,
}
}
func NewListTrackerResourcesOutput(pg *page.Page[*coredata.TrackerResource, coredata.TrackerResourceOrderField]) ListTrackerResourcesOutput {
resources := make([]*TrackerResource, 0, len(pg.Data))
for _, tr := range pg.Data {
resources = append(resources, NewTrackerResource(tr))
}
var nextCursor *page.CursorKey
if len(pg.Data) > 0 {
cursorKey := pg.Data[len(pg.Data)-1].CursorKey(pg.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListTrackerResourcesOutput{
NextCursor: nextCursor,
TrackerResources: resources,
}
}