Add MCP audit report metadata and getAuditReportUrl tool
Adds has_report, report_filename, and report_mime_type fields to Audit type to expose report attachment status. Introduces new getAuditReportUrl tool to generate presigned download URLs for audit reports. Updates NewAudit type converter to accept optional report object for enriching metadata. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -1346,11 +1346,19 @@ func (r *Resolver) GetAuditTool(ctx context.Context, req *mcp.CallToolRequest, i
|
|||||||
|
|
||||||
audit, err := prb.Audits.Get(ctx, input.ID)
|
audit, err := prb.Audits.Get(ctx, input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, types.GetAuditOutput{}, fmt.Errorf("failed to get audit: %w", err)
|
return nil, types.GetAuditOutput{}, fmt.Errorf("cannot get audit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var report *coredata.Report
|
||||||
|
if audit.ReportID != nil {
|
||||||
|
report, err = prb.Reports.Get(ctx, *audit.ReportID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.GetAuditOutput{}, fmt.Errorf("cannot get audit report: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, types.GetAuditOutput{
|
return nil, types.GetAuditOutput{
|
||||||
Audit: types.NewAudit(audit),
|
Audit: types.NewAudit(audit, report),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1375,16 +1383,16 @@ func (r *Resolver) AddAuditTool(ctx context.Context, req *mcp.CallToolRequest, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil, types.AddAuditOutput{
|
return nil, types.AddAuditOutput{
|
||||||
Audit: types.NewAudit(audit),
|
Audit: types.NewAudit(audit, nil),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAuditInput) (*mcp.CallToolResult, types.UpdateAuditOutput, error) {
|
func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAuditInput) (*mcp.CallToolResult, types.UpdateAuditOutput, error) {
|
||||||
r.MustAuthorize(ctx, input.ID, probo.ActionAuditUpdate)
|
r.MustAuthorize(ctx, input.ID, probo.ActionAuditUpdate)
|
||||||
|
|
||||||
svc := r.ProboService(ctx, input.ID)
|
prb := r.ProboService(ctx, input.ID)
|
||||||
|
|
||||||
audit, err := svc.Audits.Update(
|
audit, err := prb.Audits.Update(
|
||||||
ctx,
|
ctx,
|
||||||
&probo.UpdateAuditRequest{
|
&probo.UpdateAuditRequest{
|
||||||
ID: input.ID,
|
ID: input.ID,
|
||||||
@@ -1396,11 +1404,19 @@ func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, types.UpdateAuditOutput{}, fmt.Errorf("failed to update audit: %w", err)
|
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot update audit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var report *coredata.Report
|
||||||
|
if audit.ReportID != nil {
|
||||||
|
report, err = prb.Reports.Get(ctx, *audit.ReportID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot get audit report: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, types.UpdateAuditOutput{
|
return nil, types.UpdateAuditOutput{
|
||||||
Audit: types.NewAudit(audit),
|
Audit: types.NewAudit(audit, report),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3156,7 +3172,7 @@ func (r *Resolver) LinkFindingAuditTool(ctx context.Context, req *mcp.CallToolRe
|
|||||||
|
|
||||||
return nil, types.LinkFindingAuditOutput{
|
return nil, types.LinkFindingAuditOutput{
|
||||||
Finding: types.NewFinding(finding),
|
Finding: types.NewFinding(finding),
|
||||||
Audit: types.NewAudit(audit),
|
Audit: types.NewAudit(audit, nil),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3281,3 +3297,18 @@ func (r *Resolver) UpdateOrganizationContextTool(ctx context.Context, req *mcp.C
|
|||||||
OrganizationContext: types.NewOrganizationContext(orgContext),
|
OrganizationContext: types.NewOrganizationContext(orgContext),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) GetAuditReportUrlTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAuditReportUrlInput) (*mcp.CallToolResult, types.GetAuditReportUrlOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.ID, probo.ActionReportGetReportUrl)
|
||||||
|
|
||||||
|
prb := r.ProboService(ctx, input.ID)
|
||||||
|
|
||||||
|
url, err := prb.Audits.GenerateReportURL(ctx, input.ID, 15*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.GetAuditReportUrlOutput{}, fmt.Errorf("cannot generate audit report URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.GetAuditReportUrlOutput{
|
||||||
|
URL: *url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3935,6 +3935,7 @@ components:
|
|||||||
- framework_id
|
- framework_id
|
||||||
- state
|
- state
|
||||||
- trust_center_visibility
|
- trust_center_visibility
|
||||||
|
- has_report
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
properties:
|
properties:
|
||||||
@@ -3970,6 +3971,19 @@ components:
|
|||||||
trust_center_visibility:
|
trust_center_visibility:
|
||||||
$ref: "#/components/schemas/TrustCenterVisibility"
|
$ref: "#/components/schemas/TrustCenterVisibility"
|
||||||
description: Trust center visibility
|
description: Trust center visibility
|
||||||
|
has_report:
|
||||||
|
type: boolean
|
||||||
|
description: Whether the audit has an attached report
|
||||||
|
report_filename:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
description: Report filename, null if no report attached
|
||||||
|
report_mime_type:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
description: Report MIME type, null if no report attached
|
||||||
created_at:
|
created_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -3979,6 +3993,24 @@ components:
|
|||||||
format: date-time
|
format: date-time
|
||||||
description: Update timestamp
|
description: Update timestamp
|
||||||
|
|
||||||
|
GetAuditReportUrlInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Audit ID
|
||||||
|
|
||||||
|
GetAuditReportUrlOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- url
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
description: Presigned download URL for the report (valid for 15 minutes)
|
||||||
|
|
||||||
ListAuditsInput:
|
ListAuditsInput:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -7018,6 +7050,15 @@ tools:
|
|||||||
$ref: "#/components/schemas/UpdateAuditInput"
|
$ref: "#/components/schemas/UpdateAuditInput"
|
||||||
outputSchema:
|
outputSchema:
|
||||||
$ref: "#/components/schemas/UpdateAuditOutput"
|
$ref: "#/components/schemas/UpdateAuditOutput"
|
||||||
|
- name: getAuditReportUrl
|
||||||
|
description: Get a presigned download URL for an audit's attached report. Returns a time-limited URL valid for 15 minutes. The audit must have an attached report.
|
||||||
|
hints:
|
||||||
|
readonly: true
|
||||||
|
idempotent: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/GetAuditReportUrlInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/GetAuditReportUrlOutput"
|
||||||
- name: listControls
|
- name: listControls
|
||||||
description: List all controls for the organization or framework
|
description: List all controls for the organization or framework
|
||||||
hints:
|
hints:
|
||||||
|
|||||||
@@ -19,25 +19,33 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAudit(a *coredata.Audit) *Audit {
|
func NewAudit(a *coredata.Audit, report *coredata.Report) *Audit {
|
||||||
return &Audit{
|
audit := &Audit{
|
||||||
ID: a.ID,
|
ID: a.ID,
|
||||||
Name: a.Name,
|
Name: a.Name,
|
||||||
OrganizationID: a.OrganizationID,
|
OrganizationID: a.OrganizationID,
|
||||||
FrameworkID: a.FrameworkID,
|
FrameworkID: a.FrameworkID,
|
||||||
State: a.State,
|
State: a.State,
|
||||||
TrustCenterVisibility: a.TrustCenterVisibility,
|
TrustCenterVisibility: a.TrustCenterVisibility,
|
||||||
|
HasReport: a.ReportID != nil,
|
||||||
ValidFrom: a.ValidFrom,
|
ValidFrom: a.ValidFrom,
|
||||||
ValidUntil: a.ValidUntil,
|
ValidUntil: a.ValidUntil,
|
||||||
CreatedAt: a.CreatedAt,
|
CreatedAt: a.CreatedAt,
|
||||||
UpdatedAt: a.UpdatedAt,
|
UpdatedAt: a.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if report != nil {
|
||||||
|
audit.ReportFilename = &report.Filename
|
||||||
|
audit.ReportMimeType = &report.MimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
return audit
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListControlAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrderField]) ListControlAuditsOutput {
|
func NewListControlAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrderField]) ListControlAuditsOutput {
|
||||||
audits := make([]*Audit, 0, len(auditPage.Data))
|
audits := make([]*Audit, 0, len(auditPage.Data))
|
||||||
for _, v := range auditPage.Data {
|
for _, v := range auditPage.Data {
|
||||||
audits = append(audits, NewAudit(v))
|
audits = append(audits, NewAudit(v, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextCursor *page.CursorKey
|
var nextCursor *page.CursorKey
|
||||||
@@ -55,7 +63,7 @@ func NewListControlAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.A
|
|||||||
func NewListAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrderField]) ListAuditsOutput {
|
func NewListAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrderField]) ListAuditsOutput {
|
||||||
audits := make([]*Audit, 0, len(auditPage.Data))
|
audits := make([]*Audit, 0, len(auditPage.Data))
|
||||||
for _, v := range auditPage.Data {
|
for _, v := range auditPage.Data {
|
||||||
audits = append(audits, NewAudit(v))
|
audits = append(audits, NewAudit(v, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextCursor *page.CursorKey
|
var nextCursor *page.CursorKey
|
||||||
@@ -73,7 +81,7 @@ func NewListAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrd
|
|||||||
func NewListFindingAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrderField]) ListFindingAuditsOutput {
|
func NewListFindingAuditsOutput(auditPage *page.Page[*coredata.Audit, coredata.AuditOrderField]) ListFindingAuditsOutput {
|
||||||
audits := make([]*Audit, 0, len(auditPage.Data))
|
audits := make([]*Audit, 0, len(auditPage.Data))
|
||||||
for _, v := range auditPage.Data {
|
for _, v := range auditPage.Data {
|
||||||
audits = append(audits, NewAudit(v))
|
audits = append(audits, NewAudit(v, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextCursor *page.CursorKey
|
var nextCursor *page.CursorKey
|
||||||
|
|||||||
Reference in New Issue
Block a user