Skip to content
Open
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
54 changes: 54 additions & 0 deletions cmd/api/mcp/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Package mcp exposes the tuning operation through the Model Context Protocol.
package mcp

import (
"context"
"net/http"

mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/pgconfig/api/pkg/rules"
"github.com/pgconfig/api/pkg/version"
)

const recommendToolName = "recommend_postgres_configuration"

// NewHandler returns the stateless Streamable HTTP MCP endpoint.
func NewHandler() http.Handler {
server := mcpsdk.NewServer(&mcpsdk.Implementation{
Name: "pgconfig",
Version: version.Pretty(),
}, nil)

falseValue := false
mcpsdk.AddTool(server, &mcpsdk.Tool{
Name: recommendToolName,
Description: "Return deterministic PostgreSQL configuration " +
"recommendations for a complete tuning request.",
Annotations: &mcpsdk.ToolAnnotations{
DestructiveHint: &falseValue,
IdempotentHint: true,
OpenWorldHint: &falseValue,
ReadOnlyHint: true,
},
}, recommendPostgresConfiguration)

return mcpsdk.NewStreamableHTTPHandler(
func(*http.Request) *mcpsdk.Server { return server },
&mcpsdk.StreamableHTTPOptions{
Stateless: true,
JSONResponse: true,
},
)
}

func recommendPostgresConfiguration(
_ context.Context,
_ *mcpsdk.CallToolRequest,
request rules.TuningRequest,
) (*mcpsdk.CallToolResult, rules.TuningResult, error) {
result, err := rules.Tune(request)
if err != nil {
return nil, rules.TuningResult{}, err
}
return nil, *result, nil
}
129 changes: 129 additions & 0 deletions cmd/api/routes/mcp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package routes_test

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync"
"testing"

"github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/pgconfig/api/cmd/api/routes"
"github.com/pgconfig/api/pkg/input/bytes"
"github.com/pgconfig/api/pkg/version"
"github.com/stretchr/testify/require"
)

func TestMCPHappyPath(t *testing.T) {
app := routes.New()
httpServer := httptest.NewServer(adaptor.FiberApp(app))
t.Cleanup(httpServer.Close)

ctx := context.Background()
recorder := &responseRecorder{base: httpServer.Client().Transport}
client := mcp.NewClient(&mcp.Implementation{Name: "pgconfig-test", Version: "1.0.0"}, nil)
session, err := client.Connect(ctx, &mcp.StreamableClientTransport{
Endpoint: httpServer.URL + "/mcp",
HTTPClient: &http.Client{Transport: recorder},
DisableStandaloneSSE: true,
}, nil)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, session.Close()) })

initializeResult := session.InitializeResult()
require.Equal(t, "pgconfig", initializeResult.ServerInfo.Name)
require.Equal(t, version.Pretty(), initializeResult.ServerInfo.Version)

tools, err := session.ListTools(ctx, nil)
require.NoError(t, err)
require.Len(t, tools.Tools, 1)

tool := tools.Tools[0]
require.Equal(t, "recommend_postgres_configuration", tool.Name)
require.Contains(t, tool.Description, "deterministic")
require.NotNil(t, tool.OutputSchema)
require.NotNil(t, tool.Annotations)
require.True(t, tool.Annotations.ReadOnlyHint)
require.True(t, tool.Annotations.IdempotentHint)
require.NotNil(t, tool.Annotations.DestructiveHint)
require.False(t, *tool.Annotations.DestructiveHint)
require.NotNil(t, tool.Annotations.OpenWorldHint)
require.False(t, *tool.Annotations.OpenWorldHint)

callResult, err := session.CallTool(ctx, &mcp.CallToolParams{
Name: tool.Name,
Arguments: map[string]any{
"os": " Linux ",
"arch": " x86-64 ",
"total_ram": int64(16 * bytes.GB),
"profile": " web ",
"disk_type": " ssd ",
"max_connections": 100,
"total_cpu": 8,
"postgres_version": " 18.4 ",
},
})
require.NoError(t, err)
require.False(t, callResult.IsError)
require.Len(t, callResult.Content, 1)

structured, ok := callResult.StructuredContent.(map[string]any)
require.True(t, ok)
request := structured["request"].(map[string]any)
require.Equal(t, "linux", request["os"])
require.Equal(t, "amd64", request["arch"])
require.Equal(t, "WEB", request["profile"])
require.Equal(t, "SSD", request["disk_type"])
require.Equal(t, "18.4", request["postgres_version"])
require.Empty(t, structured["assumptions"])
require.Empty(t, structured["warnings"])

recommendations := structured["recommendations"].(map[string]any)
require.NotEmpty(t, recommendations)
require.NotContains(t, recommendations, "listen_addresses")

text, ok := callResult.Content[0].(*mcp.TextContent)
require.True(t, ok)
structuredJSON, err := json.Marshal(structured)
require.NoError(t, err)
require.JSONEq(t, string(structuredJSON), text.Text)

contentTypes := recorder.successfulPostContentTypes()
require.Len(t, contentTypes, 3)
for _, contentType := range contentTypes {
require.Equal(t, "application/json", contentType)
}

requestWithoutSession, err := http.NewRequest(http.MethodGet, httpServer.URL+"/mcp", nil)
require.NoError(t, err)
requestWithoutSession.Header.Set("Accept", "text/event-stream")
response, err := httpServer.Client().Do(requestWithoutSession)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, response.Body.Close()) })
require.Equal(t, http.StatusMethodNotAllowed, response.StatusCode)
require.Equal(t, "POST", response.Header.Get("Allow"))
}

type responseRecorder struct {
base http.RoundTripper
mu sync.Mutex
contentTypes []string
}

func (r *responseRecorder) RoundTrip(request *http.Request) (*http.Response, error) {
response, err := r.base.RoundTrip(request)
if err == nil && request.Method == http.MethodPost && response.StatusCode == http.StatusOK {
r.mu.Lock()
r.contentTypes = append(r.contentTypes, response.Header.Get("Content-Type"))
r.mu.Unlock()
}
return response, err
}

func (r *responseRecorder) successfulPostContentTypes() []string {
r.mu.Lock()
defer r.mu.Unlock()
return append([]string(nil), r.contentTypes...)
}
3 changes: 3 additions & 0 deletions cmd/api/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"fmt"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/swagger"

handV1 "github.com/pgconfig/api/cmd/api/handlers/v1"
mcpHandler "github.com/pgconfig/api/cmd/api/mcp"
)

// New create an instance of Book app routes
Expand Down Expand Up @@ -63,6 +65,7 @@ func New() *fiber.App {
})

app.Get("/v1/version", handV1.Version)
app.All("/mcp", adaptor.HTTPHandler(mcpHandler.NewHandler()))

v1 := app.Group("/v1/tuning/")

Expand Down
16 changes: 11 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/gofiber/swagger v1.1.1
github.com/mackerelio/go-osstat v0.2.6
github.com/mitchellh/go-homedir v1.1.0
github.com/modelcontextprotocol/go-sdk v1.6.1
github.com/smartystreets/goconvey v1.8.1
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
Expand Down Expand Up @@ -44,6 +45,7 @@ require (
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand All @@ -58,6 +60,8 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/smarty/assertions v1.15.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
Expand All @@ -68,13 +72,15 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.68.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.42.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
37 changes: 26 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,13 @@ github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5
github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/swagger v1.1.1 h1:FZVhVQQ9s1ZKLHL/O0loLh49bYB5l1HEAgxDlcTtkRA=
github.com/gofiber/swagger v1.1.1/go.mod h1:vtvY/sQAMc/lGTUCg0lqmBL7Ht9O7uzChpbvJeJQINw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
Expand Down Expand Up @@ -100,6 +105,8 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU=
github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
Expand All @@ -113,6 +120,10 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
Expand Down Expand Up @@ -148,6 +159,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
Expand All @@ -164,8 +177,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
Expand All @@ -175,8 +188,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -198,8 +213,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand All @@ -218,16 +233,16 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
Expand Down
5 changes: 5 additions & 0 deletions pkg/rules/tuning.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type TuningRecommendation struct {
// TuningResult is the provenance-aware result returned by Tune.
type TuningResult struct {
Request TuningRequest `json:"request"`
Assumptions []string `json:"assumptions"`
Warnings []string `json:"warnings"`
Recommendations map[string]TuningRecommendation `json:"recommendations"`
ApplicationVersion string `json:"application_version"`
legacy *category.ExportCfg
Expand Down Expand Up @@ -93,6 +95,8 @@ func Tune(request TuningRequest) (*TuningResult, error) {

return &TuningResult{
Request: normalized,
Assumptions: []string{},
Warnings: []string{},
Recommendations: recommendations,
ApplicationVersion: version.Pretty(),
legacy: legacy,
Expand All @@ -108,6 +112,7 @@ func (r *TuningResult) CompatibilityProjection() *category.ExportCfg {
func normalizeRequest(request TuningRequest) TuningRequest {
request.OS = strings.ToLower(strings.TrimSpace(request.OS))
request.Arch = strings.ToLower(strings.TrimSpace(request.Arch))
request.Profile = profile.Profile(strings.ToUpper(strings.TrimSpace(string(request.Profile))))
switch request.Arch {
case "i686":
request.Arch = "386"
Expand Down
Loading