Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions internal/apiclients/breakability/2024-10-15/gen.go

This file was deleted.

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions internal/apiclients/breakability/2025-11-05/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package v20251105

//go:generate go tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -package=v20251105 -config spec.config.yaml spec.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package: v20241015
package: v20251105
generate:
models: true
client: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,38 @@ components:
description: Resolved API version
pattern: ^((([0-9]{4})-([0-1][0-9]))-((3[01])|(0[1-9])|([12][0-9]))(~(wip|work-in-progress|experimental|beta))?)$
type: string
BreakabilityAssessmentsResponseBody:
additionalProperties: false
properties:
data:
description: Breakability assessment resources.
items:
additionalProperties: false
properties:
attributes:
$ref: '#/components/schemas/BreakabilityResponseAttributes'
id:
example: d5b640e5-d88c-4c17-9bf0-93597b7a1ce2
format: uuid
type: string
type:
$ref: '#/components/schemas/Types'
required:
- id
- type
- attributes
type: object
type: array
jsonapi:
$ref: '#/components/schemas/JsonApi'
links:
$ref: '#/components/schemas/PaginatedLinks'
type: object
BreakabilityResponseAttributes:
additionalProperties: false
properties:
package_upgrade:
$ref: '#/components/schemas/Upgrade'
public_id:
description: The stable identifier of the persisted assessment. Only present
when the assessment was successfully stored in the db. Absent if the db
Expand All @@ -194,7 +223,7 @@ components:
type: string
risk_level:
description: The estimated breakability risk level of the analyzed package
upgrades.
upgrade.
enum:
- low
- medium
Expand All @@ -205,6 +234,7 @@ components:
the estimated risk_level and possible mitigations.
type: string
required:
- package_upgrade
- risk_level
- summary
type: object
Expand Down Expand Up @@ -461,8 +491,8 @@ paths:
- OpenAPI
/orgs/{org_id}/breakability:
post:
description: Generate a breakability assessment for one or more package upgrades
operationId: createBreakabilityAnalysis
description: Generate a breakability assessment for each given package upgrade
operationId: createBreakabilityAssessments
parameters:
- description: The public ID of the organization
in: path
Expand All @@ -472,6 +502,14 @@ paths:
format: uuid
type: string
- $ref: '#/components/parameters/Version'
- description: When true, continue breakability analysis if one or more package
upgrade assessments fail. When false or omitted, stop on the first failure
(default).
in: query
name: allow_partial
schema:
default: false
type: boolean
requestBody:
content:
application/vnd.api+json:
Expand All @@ -487,7 +525,7 @@ paths:
package_upgrades:
items:
$ref: '#/components/schemas/Upgrade'
maxItems: 100
maxItems: 50
minItems: 1
type: array
required:
Expand All @@ -510,31 +548,13 @@ paths:
content:
application/vnd.api+json:
schema:
additionalProperties: false
properties:
data:
additionalProperties: false
description: breakability assessment
properties:
attributes:
$ref: '#/components/schemas/BreakabilityResponseAttributes'
id:
example: d5b640e5-d88c-4c17-9bf0-93597b7a1ce2
format: uuid
type: string
type:
$ref: '#/components/schemas/Types'
required:
- id
- type
- attributes
type: object
jsonapi:
$ref: '#/components/schemas/JsonApi'
links:
$ref: '#/components/schemas/PaginatedLinks'
type: object
description: Returns a Breakability assessment
$ref: '#/components/schemas/BreakabilityAssessmentsResponseBody'
description: Returns breakability assessments in data. When allow_partial
is false or omitted, all requested assessments succeed and data contains
one item per requested package upgrade in request order. When allow_partial=true,
data contains only successfully created assessments (which may be fewer
than requested); compare data length to the number of requested package_upgrades
to detect partial success.
headers:
deprecation:
$ref: '#/components/headers/DeprecationHeader'
Expand Down Expand Up @@ -578,11 +598,13 @@ paths:
x-snyk-api-lifecycle: released
x-snyk-api-owners:
- '@snyk/open-source_fix-analysis'
- '@snyk/open-source_open-source-leadership'
x-snyk-api-releases:
- 2024-10-15~beta
- 2025-11-05~beta
x-snyk-api-resource: breakability
x-snyk-api-stability: beta
x-snyk-api-version: 2024-10-15~beta
x-snyk-api-version: 2025-11-05~beta
x-stability-level: beta
servers:
- description: breakability-service
Expand All @@ -594,4 +616,4 @@ x-cerberus:
authentication:
strategies:
- InternalJWT: {}
x-snyk-api-version: "2024-10-15"
x-snyk-api-version: "2025-11-05"
16 changes: 15 additions & 1 deletion internal/breakability/breakability_api.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package breakability

import (
breakabilityapi "github.com/snyk/studio-mcp/internal/apiclients/breakability/2024-10-15"
breakabilityapi "github.com/snyk/studio-mcp/internal/apiclients/breakability/2025-11-05"
)

const (
Expand Down Expand Up @@ -40,6 +40,20 @@ func BuildBreakabilityResponse(attrs *breakabilityapi.BreakabilityResponseAttrib
return response
}

func SelectAssessment(body *breakabilityapi.BreakabilityAssessmentsResponseBody, upgrade PackageUpgrade) *breakabilityapi.BreakabilityResponseAttributes {
if body == nil || body.Data == nil {
return nil
}
for _, item := range *body.Data {
pu := item.Attributes.PackageUpgrade
if pu.Name == upgrade.Name && pu.FromVersion == upgrade.FromVersion && pu.ToVersion == upgrade.ToVersion {
attrs := item.Attributes
return &attrs
}
}
return nil
}

func ToAPIUpgrades(upgrades []PackageUpgrade) []breakabilityapi.Upgrade {
apiUpgrades := make([]breakabilityapi.Upgrade, len(upgrades))
for i, u := range upgrades {
Expand Down
53 changes: 52 additions & 1 deletion internal/breakability/breakability_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
package breakability

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

breakabilityapi "github.com/snyk/studio-mcp/internal/apiclients/breakability/2024-10-15"
breakabilityapi "github.com/snyk/studio-mcp/internal/apiclients/breakability/2025-11-05"
)

func TestBuildBreakabilityResponse(t *testing.T) {
Expand Down Expand Up @@ -92,6 +93,56 @@ func TestBuildBreakabilityResponse(t *testing.T) {
}
}

func TestSelectAssessment(t *testing.T) {
upgrade := PackageUpgrade{Name: "lodash", FromVersion: "4.17.10", ToVersion: "4.17.21"}

parseBody := func(jsonBody string) *breakabilityapi.BreakabilityAssessmentsResponseBody {
var body breakabilityapi.BreakabilityAssessmentsResponseBody
require.NoError(t, json.Unmarshal([]byte(jsonBody), &body))
return &body
}

t.Run("nil body returns nil", func(t *testing.T) {
assert.Nil(t, SelectAssessment(nil, upgrade))
})

t.Run("empty data returns nil", func(t *testing.T) {
assert.Nil(t, SelectAssessment(parseBody(`{"data":[]}`), upgrade))
})

t.Run("matching upgrade is returned", func(t *testing.T) {
body := parseBody(`{
"data":[{
"id":"33333333-3333-3333-3333-333333333333",
"type":"breakability",
"attributes":{
"package_upgrade":{"name":"lodash","from_version":"4.17.10","to_version":"4.17.21"},
"risk_level":"low",
"summary":"Patch only"
}
}]
}`)
result := SelectAssessment(body, upgrade)
require.NotNil(t, result)
assert.Equal(t, "Patch only", result.Summary)
})

t.Run("non-matching upgrade returns nil", func(t *testing.T) {
body := parseBody(`{
"data":[{
"id":"33333333-3333-3333-3333-333333333333",
"type":"breakability",
"attributes":{
"package_upgrade":{"name":"express","from_version":"4.0.0","to_version":"5.0.0"},
"risk_level":"high",
"summary":"Breaking"
}
}]
}`)
assert.Nil(t, SelectAssessment(body, upgrade))
})
}

func TestToAPIUpgrades(t *testing.T) {
testCases := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion internal/mcp/profiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ func TestIsToolInProfile(t *testing.T) {
{"snyk_sbom_scan", false, true, true},
{"snyk_aibom", false, true, true},
{"snyk_package_health_check", false, true, true},
{"snyk_breakability_check", false, true, true},

// Tools in experimental only
{"snyk_secret_scan", false, false, true},
{"snyk_breakability_check", false, false, true},
}

// Load actual tools from JSON
Expand Down
2 changes: 1 addition & 1 deletion internal/mcp/snyk_tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@
"description": "Runs a breaking change assessment for a package version upgrade.",
"command": [],
"standardParams": [],
"profiles": ["experimental"],
"profiles": ["full","experimental"],
"ignoreTrust": true,
"annotations": {
"readOnlyHint": true,
Expand Down
25 changes: 17 additions & 8 deletions internal/mcp/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows"
"github.com/snyk/go-application-framework/pkg/workflow"
"github.com/snyk/studio-mcp/internal/analytics"
breakabilityapi "github.com/snyk/studio-mcp/internal/apiclients/breakability/2024-10-15"
breakabilityapi "github.com/snyk/studio-mcp/internal/apiclients/breakability/2025-11-05"
packageapi "github.com/snyk/studio-mcp/internal/apiclients/package/2024-10-15"
"github.com/snyk/studio-mcp/internal/authentication"
"github.com/snyk/studio-mcp/internal/breakability"
Expand Down Expand Up @@ -764,7 +764,7 @@ func (m *McpLLMBinding) snykBreakabilityHandler(invocationCtx workflow.Invocatio

logger.Debug().Str("package", packageName).Str("from", packageFrom).Str("to", packageTo).Msg("Fetching breakability info")

const breakabilityApiVersion = "2024-10-15"
const breakabilityApiVersion = "2025-11-05"

upgrades := []breakability.PackageUpgrade{
{
Expand All @@ -774,12 +774,12 @@ func (m *McpLLMBinding) snykBreakabilityHandler(invocationCtx workflow.Invocatio
},
}

reqBody := breakabilityapi.CreateBreakabilityAnalysisApplicationVndAPIPlusJSONRequestBody{
reqBody := breakabilityapi.CreateBreakabilityAssessmentsApplicationVndAPIPlusJSONRequestBody{
Data: struct {
Attributes struct {
PackageUpgrades []breakabilityapi.Upgrade `json:"package_upgrades"`
} `json:"attributes"`
Type breakabilityapi.CreateBreakabilityAnalysisApplicationVndAPIPlusJSONBodyDataType `json:"type"`
Type breakabilityapi.CreateBreakabilityAssessmentsApplicationVndAPIPlusJSONBodyDataType `json:"type"`
}{
Type: breakabilityapi.Breakability,
Attributes: struct {
Expand All @@ -792,21 +792,30 @@ func (m *McpLLMBinding) snykBreakabilityHandler(invocationCtx workflow.Invocatio

// We want the call to fail gracefully. Since the API isn't stable enough to handle load yet.
const breakabilityErrMsg = "no additional breakability context available"
resp, err := apiClient.CreateBreakabilityAnalysisWithApplicationVndAPIPlusJSONBodyWithResponse(
allowPartial := true
resp, err := apiClient.CreateBreakabilityAssessmentsWithApplicationVndAPIPlusJSONBodyWithResponse(
ctx,
orgId,
&breakabilityapi.CreateBreakabilityAnalysisParams{Version: breakabilityApiVersion},
&breakabilityapi.CreateBreakabilityAssessmentsParams{
Version: breakabilityApiVersion,
AllowPartial: &allowPartial,
},
reqBody,
)
if err != nil {
logger.Error().Err(err).Msg("Failed to fetch breakability assessment")
return mcp.NewToolResultText(breakabilityErrMsg), nil
}
if resp.ApplicationvndApiJSON200 == nil || resp.ApplicationvndApiJSON200.Data == nil {
if resp.ApplicationvndApiJSON200 == nil {
return mcp.NewToolResultText(breakabilityErrMsg), nil
}

attrs := breakability.SelectAssessment(resp.ApplicationvndApiJSON200, upgrades[0])
if attrs == nil {
return mcp.NewToolResultText(breakabilityErrMsg), nil
}

var response = breakability.BuildBreakabilityResponse(&resp.ApplicationvndApiJSON200.Data.Attributes)
var response = breakability.BuildBreakabilityResponse(attrs)

jsonBytes, err := json.Marshal(response)
if err != nil {
Expand Down
Loading
Loading