Expose missing console capabilities on MCP

Add MCP tools and filters for mailing lists, detected trackers, compliance
portal frameworks, and document/control/framework mutations that were
available in the console but missing from MCP.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
Sacha Al Himdani
2026-07-30 17:42:57 +02:00
parent d8c284399b
commit 084726dbad
9 changed files with 1695 additions and 4 deletions

View File

@@ -112,10 +112,11 @@ type (
)
var (
ErrMissingProboService = errors.New("server configuration requires a valid probo.Service instance")
ErrMissingIAMService = errors.New("server configuration requires a valid iam.Service instance")
ErrMissingSlackService = errors.New("server configuration requires a valid slack.Service instance")
ErrMissingITAMService = errors.New("server configuration requires a valid itam.Service instance")
ErrMissingProboService = errors.New("server configuration requires a valid probo.Service instance")
ErrMissingIAMService = errors.New("server configuration requires a valid iam.Service instance")
ErrMissingSlackService = errors.New("server configuration requires a valid slack.Service instance")
ErrMissingITAMService = errors.New("server configuration requires a valid itam.Service instance")
ErrMissingMailmanService = errors.New("server configuration requires a valid mailman.Service instance")
)
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
@@ -159,6 +160,10 @@ func NewServer(cfg Config) (*Server, error) {
return nil, ErrMissingITAMService
}
if cfg.Mailman == nil {
return nil, ErrMissingMailmanService
}
csrf := http.NewCrossOriginProtection()
for _, origin := range cfg.AllowedOrigins {
if err := csrf.AddTrustedOrigin(origin); err != nil {
@@ -246,6 +251,7 @@ func NewServer(cfg Config) (*Server, error) {
cfg.CookieBanner,
cfg.RiskManagement,
cfg.ITAM,
cfg.Mailman,
cfg.TokenSecret,
cfg.File,
cfg.BaseURL,

View File

@@ -39,6 +39,7 @@ import (
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/itam"
"go.probo.inc/probo/pkg/mailman"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/prosemirror"
"go.probo.inc/probo/pkg/resourcealias"
@@ -63,6 +64,7 @@ type Resolver struct {
cookieBanner *cookiebanner.Service
riskManagement *riskmanagement.Service
itamSvc *itam.Service
mailman *mailman.Service
logger *log.Logger
fileManager *filemanager.Service
baseURL *baseurl.BaseURL

View File

@@ -21,6 +21,7 @@ import (
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/itam"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/mailman"
"go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/resourcealias"
@@ -7645,3 +7646,479 @@ func (r *Resolver) CreateDeviceTool(ctx context.Context, req *mcp.CallToolReques
EnrollmentURL: urls.EnrollmentURL,
}, nil
}
func (r *Resolver) GetThirdPartyTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetThirdPartyInput) (*mcp.CallToolResult, types.GetThirdPartyOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionThirdPartyGet)
if err != nil {
return nil, types.GetThirdPartyOutput{}, err
}
thirdParty, err := r.proboSvc.ThirdParties.Get(ctx, scope, input.ID)
if err != nil {
return nil, types.GetThirdPartyOutput{}, fmt.Errorf("cannot get third party: %w", err)
}
return nil, types.GetThirdPartyOutput{
ThirdParty: types.NewThirdParty(thirdParty),
}, nil
}
func (r *Resolver) DeleteEvidenceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteEvidenceInput) (*mcp.CallToolResult, types.DeleteEvidenceOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionEvidenceDelete)
if err != nil {
return nil, types.DeleteEvidenceOutput{}, err
}
err = r.proboSvc.Evidences.Delete(ctx, scope, input.ID)
if err != nil {
return nil, types.DeleteEvidenceOutput{}, fmt.Errorf("cannot delete evidence: %w", err)
}
return nil, types.DeleteEvidenceOutput{
DeletedEvidenceID: input.ID,
}, nil
}
func (r *Resolver) DeleteFrameworkTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteFrameworkInput) (*mcp.CallToolResult, types.DeleteFrameworkOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionFrameworkDelete)
if err != nil {
return nil, types.DeleteFrameworkOutput{}, err
}
err = r.proboSvc.Frameworks.Delete(ctx, scope, input.ID)
if err != nil {
return nil, types.DeleteFrameworkOutput{}, fmt.Errorf("cannot delete framework: %w", err)
}
return nil, types.DeleteFrameworkOutput{
DeletedFrameworkID: input.ID,
}, nil
}
func (r *Resolver) DeleteAuditReportTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteAuditReportInput) (*mcp.CallToolResult, types.DeleteAuditReportOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionAuditReportDelete)
if err != nil {
return nil, types.DeleteAuditReportOutput{}, err
}
audit, err := r.proboSvc.Audits.DeleteReport(ctx, scope, input.ID)
if err != nil {
return nil, types.DeleteAuditReportOutput{}, fmt.Errorf("cannot delete audit report: %w", err)
}
return nil, types.DeleteAuditReportOutput{
Audit: types.NewAudit(audit, nil),
}, nil
}
func (r *Resolver) DeleteControlTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteControlInput) (*mcp.CallToolResult, types.DeleteControlOutput, error) {
scope, err := r.Authorize(ctx, input.ID, probo.ActionControlDelete)
if err != nil {
return nil, types.DeleteControlOutput{}, err
}
err = r.proboSvc.Controls.Delete(ctx, scope, input.ID)
if err != nil {
return nil, types.DeleteControlOutput{}, fmt.Errorf("cannot delete control: %w", err)
}
return nil, types.DeleteControlOutput{
DeletedControlID: input.ID,
}, nil
}
func (r *Resolver) ApproveDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ApproveDocumentVersionInput) (*mcp.CallToolResult, types.ApproveDocumentVersionOutput, error) {
scope, err := r.Authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionApprove)
if err != nil {
return nil, types.ApproveDocumentVersionOutput{}, err
}
identity := authn.IdentityFromContext(ctx)
signer := signerMetadataFromToolRequest(req)
decision, err := r.proboSvc.DocumentApprovals.Approve(
ctx,
scope,
probo.ApproveDocumentVersionRequest{
DocumentVersionID: input.DocumentVersionID,
IdentityID: identity.ID,
Comment: input.Comment,
SignerFullName: identity.FullName,
SignerEmail: identity.EmailAddress,
SignerIPAddr: signer.IPAddr,
SignerUA: signer.UA,
},
)
if err != nil {
return nil, types.ApproveDocumentVersionOutput{}, fmt.Errorf("cannot approve document version: %w", err)
}
return nil, types.ApproveDocumentVersionOutput{
DocumentVersionApprovalDecision: types.NewDocumentVersionApprovalDecision(decision),
}, nil
}
func (r *Resolver) RejectDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RejectDocumentVersionInput) (*mcp.CallToolResult, types.RejectDocumentVersionOutput, error) {
scope, err := r.Authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionReject)
if err != nil {
return nil, types.RejectDocumentVersionOutput{}, err
}
identity := authn.IdentityFromContext(ctx)
decision, err := r.proboSvc.DocumentApprovals.Reject(
ctx,
scope,
probo.RejectDocumentVersionRequest{
DocumentVersionID: input.DocumentVersionID,
IdentityID: identity.ID,
Comment: input.Comment,
},
)
if err != nil {
return nil, types.RejectDocumentVersionOutput{}, fmt.Errorf("cannot reject document version: %w", err)
}
return nil, types.RejectDocumentVersionOutput{
DocumentVersionApprovalDecision: types.NewDocumentVersionApprovalDecision(decision),
}, nil
}
func (r *Resolver) SignDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.SignDocumentInput) (*mcp.CallToolResult, types.SignDocumentOutput, error) {
scope, err := r.Authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSign)
if err != nil {
return nil, types.SignDocumentOutput{}, err
}
identity := authn.IdentityFromContext(ctx)
signer := signerMetadataFromToolRequest(req)
signature, err := r.proboSvc.Documents.SignDocumentVersionByIdentity(
ctx,
scope,
probo.SignDocumentVersionRequest{
DocumentVersionID: input.DocumentVersionID,
IdentityID: identity.ID,
SignerFullName: identity.FullName,
SignerEmail: identity.EmailAddress,
SignerIPAddr: signer.IPAddr,
SignerUA: signer.UA,
},
)
if err != nil {
return nil, types.SignDocumentOutput{}, fmt.Errorf("cannot sign document: %w", err)
}
return nil, types.SignDocumentOutput{
DocumentVersionSignature: types.NewDocumentVersionSignature(signature),
}, nil
}
func (r *Resolver) GetMailingListTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetMailingListInput) (*mcp.CallToolResult, types.GetMailingListOutput, error) {
scope, err := r.Authorize(ctx, input.CompliancePortalID, management.ActionMailingListSubscriberList)
if err != nil {
return nil, types.GetMailingListOutput{}, err
}
mailingList, err := r.management.GetMailingList(ctx, scope, input.CompliancePortalID)
if err != nil {
return nil, types.GetMailingListOutput{}, fmt.Errorf("cannot get mailing list: %w", err)
}
return nil, types.GetMailingListOutput{MailingList: types.NewMailingList(mailingList)}, nil
}
func (r *Resolver) UpdateMailingListTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateMailingListInput) (*mcp.CallToolResult, types.UpdateMailingListOutput, error) {
if _, err := r.Authorize(ctx, input.ID, management.ActionMailingListUpdate); err != nil {
return nil, types.UpdateMailingListOutput{}, err
}
mailingList, err := r.mailman.UpdateMailingList(ctx, input.ID, input.ReplyTo)
if err != nil {
return nil, types.UpdateMailingListOutput{}, fmt.Errorf("cannot update mailing list: %w", err)
}
return nil, types.UpdateMailingListOutput{MailingList: types.NewMailingList(mailingList)}, nil
}
func (r *Resolver) ListMailingListSubscribersTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMailingListSubscribersInput) (*mcp.CallToolResult, types.ListMailingListSubscribersOutput, error) {
if _, err := r.Authorize(ctx, input.MailingListID, management.ActionMailingListSubscriberList); err != nil {
return nil, types.ListMailingListSubscribersOutput{}, err
}
pageOrderBy := page.OrderBy[coredata.MailingListSubscriberOrderField]{
Field: coredata.MailingListSubscriberOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.MailingListSubscriberOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := r.mailman.ListSubscribers(ctx, input.MailingListID, cursor)
if err != nil {
return nil, types.ListMailingListSubscribersOutput{}, fmt.Errorf("cannot list mailing list subscribers: %w", err)
}
return nil, types.NewListMailingListSubscribersOutput(p), nil
}
func (r *Resolver) AddMailingListSubscriberTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddMailingListSubscriberInput) (*mcp.CallToolResult, types.AddMailingListSubscriberOutput, error) {
if _, err := r.Authorize(ctx, input.MailingListID, management.ActionMailingListSubscriberCreate); err != nil {
return nil, types.AddMailingListSubscriberOutput{}, err
}
subscriber, err := r.mailman.CreateSubscriber(
ctx,
&mailman.CreateSubscriberRequest{
MailingListID: input.MailingListID,
Email: input.Email,
FullName: input.FullName,
Confirmed: input.Confirmed != nil && *input.Confirmed,
},
)
if err != nil {
return nil, types.AddMailingListSubscriberOutput{}, fmt.Errorf("cannot add mailing list subscriber: %w", err)
}
return nil, types.AddMailingListSubscriberOutput{
MailingListSubscriber: types.NewMailingListSubscriber(subscriber),
}, nil
}
func (r *Resolver) DeleteMailingListSubscriberTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteMailingListSubscriberInput) (*mcp.CallToolResult, types.DeleteMailingListSubscriberOutput, error) {
if _, err := r.Authorize(ctx, input.ID, management.ActionMailingListSubscriberDelete); err != nil {
return nil, types.DeleteMailingListSubscriberOutput{}, err
}
if err := r.mailman.DeleteSubscriber(ctx, input.ID); err != nil {
return nil, types.DeleteMailingListSubscriberOutput{}, fmt.Errorf("cannot delete mailing list subscriber: %w", err)
}
return nil, types.DeleteMailingListSubscriberOutput{
DeletedMailingListSubscriberID: input.ID,
}, nil
}
func (r *Resolver) ListMailingListUpdatesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMailingListUpdatesInput) (*mcp.CallToolResult, types.ListMailingListUpdatesOutput, error) {
if _, err := r.Authorize(ctx, input.MailingListID, management.ActionMailingListUpdateList); err != nil {
return nil, types.ListMailingListUpdatesOutput{}, err
}
pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{
Field: coredata.MailingListUpdateOrderFieldUpdatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.MailingListUpdateOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := r.mailman.ListMailingListUpdates(ctx, input.MailingListID, cursor)
if err != nil {
return nil, types.ListMailingListUpdatesOutput{}, fmt.Errorf("cannot list mailing list updates: %w", err)
}
return nil, types.NewListMailingListUpdatesOutput(p), nil
}
func (r *Resolver) AddMailingListUpdateTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddMailingListUpdateInput) (*mcp.CallToolResult, types.AddMailingListUpdateOutput, error) {
if _, err := r.Authorize(ctx, input.MailingListID, management.ActionMailingListUpdateCreate); err != nil {
return nil, types.AddMailingListUpdateOutput{}, err
}
update, err := r.mailman.CreateMailingListUpdate(
ctx,
&mailman.CreateMailingListUpdateRequest{
MailingListID: input.MailingListID,
Title: input.Title,
Body: input.Body,
},
)
if err != nil {
return nil, types.AddMailingListUpdateOutput{}, fmt.Errorf("cannot add mailing list update: %w", err)
}
return nil, types.AddMailingListUpdateOutput{
MailingListUpdate: types.NewMailingListUpdate(update),
}, nil
}
func (r *Resolver) UpdateMailingListUpdateTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateMailingListUpdateInput) (*mcp.CallToolResult, types.UpdateMailingListUpdateOutput, error) {
if _, err := r.Authorize(ctx, input.ID, management.ActionMailingListUpdateUpdate); err != nil {
return nil, types.UpdateMailingListUpdateOutput{}, err
}
update, err := r.mailman.UpdateMailingListUpdate(
ctx,
&mailman.UpdateMailingListUpdateRequest{
ID: input.ID,
Title: input.Title,
Body: input.Body,
},
)
if err != nil {
return nil, types.UpdateMailingListUpdateOutput{}, fmt.Errorf("cannot update mailing list update: %w", err)
}
return nil, types.UpdateMailingListUpdateOutput{
MailingListUpdate: types.NewMailingListUpdate(update),
}, nil
}
func (r *Resolver) SendMailingListUpdateTool(ctx context.Context, req *mcp.CallToolRequest, input *types.SendMailingListUpdateInput) (*mcp.CallToolResult, types.SendMailingListUpdateOutput, error) {
if _, err := r.Authorize(ctx, input.ID, management.ActionMailingListUpdateUpdate); err != nil {
return nil, types.SendMailingListUpdateOutput{}, err
}
update, err := r.mailman.SendMailingListUpdate(ctx, input.ID)
if err != nil {
return nil, types.SendMailingListUpdateOutput{}, fmt.Errorf("cannot send mailing list update: %w", err)
}
return nil, types.SendMailingListUpdateOutput{
MailingListUpdate: types.NewMailingListUpdate(update),
}, nil
}
func (r *Resolver) DeleteMailingListUpdateTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteMailingListUpdateInput) (*mcp.CallToolResult, types.DeleteMailingListUpdateOutput, error) {
if _, err := r.Authorize(ctx, input.ID, management.ActionMailingListUpdateDelete); err != nil {
return nil, types.DeleteMailingListUpdateOutput{}, err
}
if err := r.mailman.DeleteMailingListUpdate(ctx, input.ID); err != nil {
return nil, types.DeleteMailingListUpdateOutput{}, fmt.Errorf("cannot delete mailing list update: %w", err)
}
return nil, types.DeleteMailingListUpdateOutput{
DeletedMailingListUpdateID: input.ID,
}, nil
}
func (r *Resolver) ListDetectedTrackersTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDetectedTrackersInput) (*mcp.CallToolResult, types.ListDetectedTrackersOutput, error) {
scope, err := r.Authorize(ctx, input.TrackerPatternID, probo.ActionTrackerPatternGet)
if err != nil {
return nil, types.ListDetectedTrackersOutput{}, err
}
pageOrderBy := page.OrderBy[coredata.DetectedTrackerOrderField]{
Field: coredata.DetectedTrackerOrderFieldLastDetectedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.DetectedTrackerOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
trackers, err := r.cookieBanner.ListDetectedTrackersForPattern(ctx, scope, input.TrackerPatternID, cursor)
if err != nil {
return nil, types.ListDetectedTrackersOutput{}, fmt.Errorf("cannot list detected trackers: %w", err)
}
return nil, types.NewListDetectedTrackersOutput(page.NewPage(trackers, cursor)), nil
}
func (r *Resolver) ListCompliancePortalFrameworksTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCompliancePortalFrameworksInput) (*mcp.CallToolResult, types.ListCompliancePortalFrameworksOutput, error) {
scope, err := r.Authorize(ctx, input.CompliancePortalID, management.ActionComplianceFrameworkList)
if err != nil {
return nil, types.ListCompliancePortalFrameworksOutput{}, err
}
pageOrderBy := page.OrderBy[coredata.ComplianceFrameworkOrderField]{
Field: coredata.ComplianceFrameworkOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.ComplianceFrameworkOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := r.management.ListFrameworksWithHidden(ctx, scope, input.CompliancePortalID, cursor)
if err != nil {
return nil, types.ListCompliancePortalFrameworksOutput{}, fmt.Errorf("cannot list compliance portal frameworks: %w", err)
}
return nil, types.NewListCompliancePortalFrameworksOutput(p), nil
}
func (r *Resolver) AddCompliancePortalFrameworkTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCompliancePortalFrameworkInput) (*mcp.CallToolResult, types.AddCompliancePortalFrameworkOutput, error) {
scope, err := r.Authorize(ctx, input.CompliancePortalID, management.ActionComplianceFrameworkCreate)
if err != nil {
return nil, types.AddCompliancePortalFrameworkOutput{}, err
}
framework, err := r.management.CreateFramework(
ctx,
scope,
&management.CreateFrameworkRequest{
CompliancePortalID: input.CompliancePortalID,
FrameworkID: input.FrameworkID,
},
)
if err != nil {
return nil, types.AddCompliancePortalFrameworkOutput{}, fmt.Errorf("cannot add compliance portal framework: %w", err)
}
return nil, types.AddCompliancePortalFrameworkOutput{
CompliancePortalFramework: types.NewCompliancePortalFramework(framework),
}, nil
}
func (r *Resolver) UpdateCompliancePortalFrameworkTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCompliancePortalFrameworkInput) (*mcp.CallToolResult, types.UpdateCompliancePortalFrameworkOutput, error) {
scope, err := r.Authorize(ctx, input.ID, management.ActionComplianceFrameworkUpdateRank)
if err != nil {
return nil, types.UpdateCompliancePortalFrameworkOutput{}, err
}
framework, err := r.management.UpdateFramework(
ctx,
scope,
&management.UpdateFrameworkRequest{
ID: input.ID,
Rank: input.Rank,
},
)
if err != nil {
return nil, types.UpdateCompliancePortalFrameworkOutput{}, fmt.Errorf("cannot update compliance portal framework: %w", err)
}
return nil, types.UpdateCompliancePortalFrameworkOutput{
CompliancePortalFramework: types.NewCompliancePortalFramework(framework),
}, nil
}
func (r *Resolver) DeleteCompliancePortalFrameworkTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCompliancePortalFrameworkInput) (*mcp.CallToolResult, types.DeleteCompliancePortalFrameworkOutput, error) {
scope, err := r.Authorize(ctx, input.ID, management.ActionComplianceFrameworkDelete)
if err != nil {
return nil, types.DeleteCompliancePortalFrameworkOutput{}, err
}
err = r.management.DeleteFramework(
ctx,
scope,
&management.DeleteFrameworkRequest{ID: input.ID},
)
if err != nil {
return nil, types.DeleteCompliancePortalFrameworkOutput{}, fmt.Errorf("cannot delete compliance portal framework: %w", err)
}
return nil, types.DeleteCompliancePortalFrameworkOutput{
DeletedCompliancePortalFrameworkID: input.ID,
}, nil
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package mcp_v1
import (
"net/http"
"github.com/modelcontextprotocol/go-sdk/mcp"
"go.probo.inc/probo/pkg/server/api/clientip"
)
type signerMetadata struct {
IPAddr string
UA string
}
func signerMetadataFromToolRequest(req *mcp.CallToolRequest) signerMetadata {
if req == nil || req.Extra == nil || req.Extra.Header == nil {
return signerMetadata{}
}
httpReq := &http.Request{Header: req.Extra.Header}
return signerMetadata{
IPAddr: clientip.Extract(httpReq),
UA: req.Extra.Header.Get("User-Agent"),
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package types
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/page"
)
func NewCompliancePortalFramework(cf *coredata.ComplianceFramework) *CompliancePortalFramework {
return &CompliancePortalFramework{
ID: cf.ID,
OrganizationID: cf.OrganizationID,
CompliancePortalID: cf.CompliancePortalID,
FrameworkID: cf.FrameworkID,
Rank: cf.Rank,
Visibility: cf.Visibility,
CreatedAt: cf.CreatedAt,
UpdatedAt: cf.UpdatedAt,
}
}
func NewListCompliancePortalFrameworksOutput(
p *page.Page[*coredata.ComplianceFramework, coredata.ComplianceFrameworkOrderField],
) ListCompliancePortalFrameworksOutput {
frameworks := make([]*CompliancePortalFramework, 0, len(p.Data))
for _, cf := range p.Data {
frameworks = append(frameworks, NewCompliancePortalFramework(cf))
}
var nextCursor *page.CursorKey
if len(p.Data) > 0 {
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListCompliancePortalFrameworksOutput{
NextCursor: nextCursor,
CompliancePortalFrameworks: frameworks,
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package types
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/page"
)
func NewDetectedTracker(dt *coredata.DetectedTracker) *DetectedTracker {
var source *DetectedTrackerSource
if dt.Source != nil {
s := DetectedTrackerSource(string(*dt.Source))
source = &s
}
return &DetectedTracker{
ID: dt.ID,
Identifier: dt.Identifier,
InitiatorURL: dt.InitiatorURL,
MaxAgeSeconds: dt.MaxAgeSeconds,
Source: source,
LastDetectedAt: dt.LastDetectedAt,
CreatedAt: dt.CreatedAt,
}
}
func NewListDetectedTrackersOutput(
p *page.Page[*coredata.DetectedTracker, coredata.DetectedTrackerOrderField],
) ListDetectedTrackersOutput {
trackers := make([]*DetectedTracker, 0, len(p.Data))
for _, dt := range p.Data {
trackers = append(trackers, NewDetectedTracker(dt))
}
var nextCursor *page.CursorKey
if len(p.Data) > 0 {
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListDetectedTrackersOutput{
NextCursor: nextCursor,
DetectedTrackers: trackers,
}
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package types
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/page"
)
func NewMailingList(ml *coredata.MailingList) *MailingList {
if ml == nil {
return nil
}
return &MailingList{
ID: ml.ID,
OrganizationID: ml.OrganizationID,
ReplyTo: ml.ReplyTo,
CreatedAt: ml.CreatedAt,
UpdatedAt: ml.UpdatedAt,
}
}
func NewMailingListSubscriber(s *coredata.MailingListSubscriber) *MailingListSubscriber {
return &MailingListSubscriber{
ID: s.ID,
OrganizationID: s.OrganizationID,
MailingListID: s.MailingListID,
FullName: s.FullName,
Email: s.Email,
Status: s.Status,
CreatedAt: s.CreatedAt,
UpdatedAt: s.UpdatedAt,
}
}
func NewListMailingListSubscribersOutput(
p *page.Page[*coredata.MailingListSubscriber, coredata.MailingListSubscriberOrderField],
) ListMailingListSubscribersOutput {
subscribers := make([]*MailingListSubscriber, 0, len(p.Data))
for _, s := range p.Data {
subscribers = append(subscribers, NewMailingListSubscriber(s))
}
var nextCursor *page.CursorKey
if len(p.Data) > 0 {
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListMailingListSubscribersOutput{
NextCursor: nextCursor,
MailingListSubscribers: subscribers,
}
}
func NewMailingListUpdate(u *coredata.MailingListUpdate) *MailingListUpdate {
return &MailingListUpdate{
ID: u.ID,
OrganizationID: u.OrganizationID,
MailingListID: u.MailingListID,
Title: u.Title,
Body: u.Body,
Status: u.Status,
CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt,
}
}
func NewListMailingListUpdatesOutput(
p *page.Page[*coredata.MailingListUpdate, coredata.MailingListUpdateOrderField],
) ListMailingListUpdatesOutput {
updates := make([]*MailingListUpdate, 0, len(p.Data))
for _, u := range p.Data {
updates = append(updates, NewMailingListUpdate(u))
}
var nextCursor *page.CursorKey
if len(p.Data) > 0 {
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListMailingListUpdatesOutput{
NextCursor: nextCursor,
MailingListUpdates: updates,
}
}

View File

@@ -35,6 +35,7 @@ import (
"go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/itam"
"go.probo.inc/probo/pkg/mailman"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/resourcealias"
"go.probo.inc/probo/pkg/riskmanagement"
@@ -56,6 +57,7 @@ func NewMux(
cookieBannerSvc *cookiebanner.Service,
riskManagementSvc *riskmanagement.Service,
itamSvc *itam.Service,
mailmanSvc *mailman.Service,
tokenSecret string,
fileManagerSvc *filemanager.Service,
baseURL *baseurl.BaseURL,
@@ -75,6 +77,7 @@ func NewMux(
cookieBanner: cookieBannerSvc,
riskManagement: riskManagementSvc,
itamSvc: itamSvc,
mailman: mailmanSvc,
logger: logger,
fileManager: fileManagerSvc,
baseURL: baseURL,