← back to Cli Printing Press
feat(cli): compact repeated MCP parameter descriptions (#569)
314b5650f4025eda9c49fc6cac8fb50b1da29aae · 2026-05-03 23:03:51 -0700 · Trevin Chow
Files touched
M internal/generator/generator.goM internal/generator/generator_test.goM internal/generator/templates/mcp_tools.go.tmplA internal/mcpdesc/params.goA internal/mcpdesc/params_test.goM internal/pipeline/toolsmanifest.goM internal/pipeline/toolsmanifest_test.go
Diff
commit 314b5650f4025eda9c49fc6cac8fb50b1da29aae
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sun May 3 23:03:51 2026 -0700
feat(cli): compact repeated MCP parameter descriptions (#569)
---
internal/generator/generator.go | 10 ++
internal/generator/generator_test.go | 44 ++++++++
internal/generator/templates/mcp_tools.go.tmpl | 12 +--
internal/mcpdesc/params.go | 135 +++++++++++++++++++++++++
internal/mcpdesc/params_test.go | 107 ++++++++++++++++++++
internal/pipeline/toolsmanifest.go | 30 +++++-
internal/pipeline/toolsmanifest_test.go | 84 +++++++++++++++
7 files changed, 411 insertions(+), 11 deletions(-)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 518fc78d..68f47152 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -150,6 +150,8 @@ type Generator struct {
profile *profiler.APIProfile
funcs template.FuncMap
templates map[string]*template.Template
+
+ mcpParamDescriptions *mcpdesc.ParamDescriptionCompactor
}
func New(s *spec.APISpec, outputDir string) *Generator {
@@ -211,6 +213,7 @@ func New(s *spec.APISpec, outputDir string) *Generator {
"add": func(a, b int) int { return a + b },
"oneline": naming.OneLine,
"composeMCPDesc": composeMCPDesc,
+ "mcpParamDesc": g.mcpParamDescription,
"flagName": flagName,
"paramIdent": paramIdent,
"safeTypeName": safeTypeName,
@@ -2870,6 +2873,13 @@ func composeMCPDesc(endpoint spec.Endpoint, noAuth bool, authType string, public
})
}
+func (g *Generator) mcpParamDescription(p spec.Param) string {
+ if g.mcpParamDescriptions == nil {
+ g.mcpParamDescriptions = mcpdesc.NewParamDescriptionCompactor(g.Spec)
+ }
+ return naming.OneLine(g.mcpParamDescriptions.Description(p))
+}
+
func exampleValue(p spec.Param) string {
nameLower := strings.ToLower(p.Name)
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 0e60c48a..f4786fcf 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -4129,6 +4129,50 @@ func TestGenerateMCPContextEscapesDomainStrings(t *testing.T) {
assert.Contains(t, src, `filter=\"active\"`)
}
+func TestGenerateMCPCompactsRepeatedParamDescriptions(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("mcp-dedupe")
+ sharedDescription := "Select additional nested resource fields to include in the response. Use comma-separated field names such as owner, permissions, metadata, relationships, and auditTrail; unsupported values are ignored by the upstream API."
+ apiSpec.Resources = map[string]spec.Resource{
+ "items": {
+ Description: "Manage items",
+ Endpoints: map[string]spec.Endpoint{
+ "list": {
+ Method: "GET",
+ Path: "/items",
+ Description: "List items",
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: sharedDescription}},
+ },
+ "search": {
+ Method: "GET",
+ Path: "/items/search",
+ Description: "Search items",
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: sharedDescription}},
+ },
+ "recent": {
+ Method: "GET",
+ Path: "/items/recent",
+ Description: "List recent items",
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: sharedDescription}},
+ },
+ },
+ },
+ }
+
+ outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+ require.NoError(t, New(apiSpec, outputDir).Generate())
+
+ toolsData, err := os.ReadFile(filepath.Join(outputDir, "internal", "mcp", "tools.go"))
+ require.NoError(t, err)
+ toolsBody := string(toolsData)
+
+ assert.NotContains(t, toolsBody, sharedDescription,
+ "generated MCP runtime schema should not repeat long shared parameter descriptions verbatim")
+ assert.Equal(t, 3, strings.Count(toolsBody, `mcplib.Description("Select additional nested resource fields to include in the response.")`),
+ "shared param descriptions should stay understandable after compaction")
+}
+
func TestEnvVarBuiltinFieldDedup(t *testing.T) {
t.Parallel()
tests := []struct {
diff --git a/internal/generator/templates/mcp_tools.go.tmpl b/internal/generator/templates/mcp_tools.go.tmpl
index 5227f699..db9ee8d6 100644
--- a/internal/generator/templates/mcp_tools.go.tmpl
+++ b/internal/generator/templates/mcp_tools.go.tmpl
@@ -41,11 +41,11 @@ func RegisterTools(s *server.MCPServer) {
mcplib.WithDescription({{printf "%q" (composeMCPDesc $endpoint $endpoint.NoAuth $.Auth.Type $.MCPPublicCount $.MCPTotalCount)}}),
{{- range $endpoint.Params}}
{{- if eq .Type "integer"}}
- mcplib.WithNumber({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (oneline .Description)}})),
+ mcplib.WithNumber({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (mcpParamDesc .)}})),
{{- else if eq .Type "boolean"}}
- mcplib.WithBoolean({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (oneline .Description)}})),
+ mcplib.WithBoolean({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (mcpParamDesc .)}})),
{{- else}}
- mcplib.WithString({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (oneline .Description)}})),
+ mcplib.WithString({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (mcpParamDesc .)}})),
{{- end}}
{{- end}}
{{- if eq (upper $endpoint.Method) "GET"}}
@@ -72,11 +72,11 @@ func RegisterTools(s *server.MCPServer) {
mcplib.WithDescription({{printf "%q" (composeMCPDesc $endpoint $endpoint.NoAuth $.Auth.Type $.MCPPublicCount $.MCPTotalCount)}}),
{{- range $endpoint.Params}}
{{- if eq .Type "integer"}}
- mcplib.WithNumber({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (oneline .Description)}})),
+ mcplib.WithNumber({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (mcpParamDesc .)}})),
{{- else if eq .Type "boolean"}}
- mcplib.WithBoolean({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (oneline .Description)}})),
+ mcplib.WithBoolean({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (mcpParamDesc .)}})),
{{- else}}
- mcplib.WithString({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (oneline .Description)}})),
+ mcplib.WithString({{printf "%q" .Name}}{{if .Required}}, mcplib.Required(){{end}}, mcplib.Description({{printf "%q" (mcpParamDesc .)}})),
{{- end}}
{{- end}}
{{- if eq (upper $endpoint.Method) "GET"}}
diff --git a/internal/mcpdesc/params.go b/internal/mcpdesc/params.go
new file mode 100644
index 00000000..cedfefd5
--- /dev/null
+++ b/internal/mcpdesc/params.go
@@ -0,0 +1,135 @@
+package mcpdesc
+
+import (
+ "strings"
+ "unicode/utf8"
+
+ "github.com/mvanhorn/cli-printing-press/v3/internal/naming"
+ "github.com/mvanhorn/cli-printing-press/v3/internal/spec"
+)
+
+const (
+ sharedParamMinOccurrences = 3
+ sharedParamMinDescription = 100
+ sharedParamMinSentence = 24
+ sharedParamDescriptionMax = 96
+ sharedParamDescriptionTail = "..."
+)
+
+// ParamDescriptionCompactor shortens descriptions only for long, repeated
+// parameter text that appears often enough to bloat endpoint-mirror MCP
+// schemas. Unique, short, and endpoint-specific descriptions pass through.
+type ParamDescriptionCompactor struct {
+ compacted map[paramDescriptionKey]string
+}
+
+type paramDescriptionKey struct {
+ name string
+ paramType string
+ description string
+}
+
+func NewParamDescriptionCompactor(api *spec.APISpec) *ParamDescriptionCompactor {
+ if api == nil {
+ return NewParamDescriptionCompactorForEndpoints(nil)
+ }
+ var endpoints []spec.Endpoint
+ for _, resource := range api.Resources {
+ endpoints = appendResourceEndpoints(endpoints, resource)
+ }
+ return NewParamDescriptionCompactorForEndpoints(endpoints)
+}
+
+func NewParamDescriptionCompactorForEndpoints(endpoints []spec.Endpoint) *ParamDescriptionCompactor {
+ counts := map[paramDescriptionKey]int{}
+ for _, endpoint := range endpoints {
+ countParams(counts, endpoint.Params)
+ countParams(counts, endpoint.Body)
+ }
+
+ compacted := map[paramDescriptionKey]string{}
+ for key, count := range counts {
+ if count >= sharedParamMinOccurrences && utf8.RuneCountInString(key.description) >= sharedParamMinDescription {
+ compacted[key] = compactSharedParamDescription(key.description)
+ }
+ }
+ return &ParamDescriptionCompactor{compacted: compacted}
+}
+
+func (c *ParamDescriptionCompactor) Description(p spec.Param) string {
+ description := naming.OneLineNormalize(p.Description)
+ if c == nil || description == "" {
+ return description
+ }
+ if compacted, ok := c.compacted[keyForParamDescription(p, description)]; ok {
+ return compacted
+ }
+ return description
+}
+
+func appendResourceEndpoints(endpoints []spec.Endpoint, resource spec.Resource) []spec.Endpoint {
+ for _, endpoint := range resource.Endpoints {
+ endpoints = append(endpoints, endpoint)
+ }
+ for _, subResource := range resource.SubResources {
+ endpoints = appendResourceEndpoints(endpoints, subResource)
+ }
+ return endpoints
+}
+
+func countParams(counts map[paramDescriptionKey]int, params []spec.Param) {
+ for _, p := range params {
+ description := naming.OneLineNormalize(p.Description)
+ if description == "" {
+ continue
+ }
+ counts[keyForParamDescription(p, description)]++
+ }
+}
+
+func keyForParamDescription(p spec.Param, description string) paramDescriptionKey {
+ return paramDescriptionKey{
+ name: strings.ToLower(strings.TrimSpace(p.Name)),
+ paramType: normalizeParamType(p.Type),
+ description: description,
+ }
+}
+
+func normalizeParamType(paramType string) string {
+ normalized := strings.ToLower(strings.TrimSpace(paramType))
+ if normalized == "" {
+ return "string"
+ }
+ return normalized
+}
+
+func compactSharedParamDescription(description string) string {
+ if first := firstSentence(description); len(first) >= sharedParamMinSentence && len(first) <= sharedParamDescriptionMax {
+ return first
+ }
+ return truncateSharedParamDescription(description)
+}
+
+func firstSentence(description string) string {
+ for i, r := range description {
+ if r == '.' || r == '!' || r == '?' {
+ return strings.TrimSpace(description[:i+1])
+ }
+ }
+ return ""
+}
+
+func truncateSharedParamDescription(description string) string {
+ runes := []rune(description)
+ if len(runes) <= sharedParamDescriptionMax {
+ return description
+ }
+ limit := sharedParamDescriptionMax - len(sharedParamDescriptionTail)
+ cut := limit
+ prefix := string(runes[:limit])
+ if idx := strings.LastIndexAny(prefix, " \t"); idx > 0 {
+ cut = idx
+ return strings.TrimSpace(prefix[:cut]) + sharedParamDescriptionTail
+ }
+ return strings.TrimSpace(string(runes[:cut])) + sharedParamDescriptionTail
+}
diff --git a/internal/mcpdesc/params_test.go b/internal/mcpdesc/params_test.go
new file mode 100644
index 00000000..64aa0f79
--- /dev/null
+++ b/internal/mcpdesc/params_test.go
@@ -0,0 +1,107 @@
+package mcpdesc
+
+import (
+ "strings"
+ "testing"
+ "unicode/utf8"
+
+ "github.com/mvanhorn/cli-printing-press/v3/internal/naming"
+ "github.com/mvanhorn/cli-printing-press/v3/internal/spec"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParamDescriptionCompactorPassesThroughUniqueAndShortDescriptions(t *testing.T) {
+ uniqueDescription := "Unique endpoint-specific filter text."
+ api := &spec.APISpec{
+ Resources: map[string]spec.Resource{
+ "items": {
+ Endpoints: map[string]spec.Endpoint{
+ "list": {
+ Params: []spec.Param{
+ {Name: "expand", Type: "string", Description: uniqueDescription},
+ {Name: "limit", Type: "integer", Description: "Max items to return"},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ compactor := NewParamDescriptionCompactor(api)
+
+ assert.Equal(t, uniqueDescription, compactor.Description(spec.Param{Name: "expand", Type: "string", Description: uniqueDescription}))
+ assert.Equal(t, "Max items to return", compactor.Description(spec.Param{Name: "limit", Type: "integer", Description: "Max items to return"}))
+}
+
+func TestParamDescriptionCompactorUsesFullDescriptionsForKeysAndPassThrough(t *testing.T) {
+ sharedPrefix := strings.Repeat("Shared endpoint expansion guidance ", 5)
+ ownerDescription := sharedPrefix + "Allowed values: owner, creator, updater, and permissionSummary."
+ statusDescription := sharedPrefix + "Allowed values: status, lifecycle, archived, and moderationState."
+ auditDescription := sharedPrefix + "Allowed values: createdAt, updatedAt, actor, and requestId."
+ api := &spec.APISpec{
+ Resources: map[string]spec.Resource{
+ "items": {
+ Endpoints: map[string]spec.Endpoint{
+ "list": {
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: ownerDescription}},
+ },
+ "search": {
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: statusDescription}},
+ },
+ "recent": {
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: auditDescription}},
+ },
+ },
+ },
+ },
+ }
+
+ compactor := NewParamDescriptionCompactor(api)
+
+ assert.Equal(t, naming.OneLineNormalize(ownerDescription), compactor.Description(spec.Param{Name: "expand", Type: "string", Description: ownerDescription}))
+ assert.Equal(t, naming.OneLineNormalize(statusDescription), compactor.Description(spec.Param{Name: "expand", Type: "string", Description: statusDescription}))
+ assert.Equal(t, naming.OneLineNormalize(auditDescription), compactor.Description(spec.Param{Name: "expand", Type: "string", Description: auditDescription}))
+}
+
+func TestParamDescriptionCompactorNormalizesEmptyStringTypes(t *testing.T) {
+ description := "Select additional nested resource fields to include in the response. Use comma-separated field names such as owner, permissions, metadata, relationships, and auditTrail; unsupported values are ignored by the upstream API."
+ api := &spec.APISpec{
+ Resources: map[string]spec.Resource{
+ "items": {
+ Endpoints: map[string]spec.Endpoint{
+ "list": {Params: []spec.Param{{Name: "expand", Description: description}}},
+ "search": {Params: []spec.Param{{Name: "expand", Type: "string", Description: description}}},
+ "recent": {Params: []spec.Param{{Name: "expand", Description: description}}},
+ },
+ },
+ },
+ }
+
+ compactor := NewParamDescriptionCompactor(api)
+
+ assert.Equal(t,
+ "Select additional nested resource fields to include in the response.",
+ compactor.Description(spec.Param{Name: "expand", Type: "string", Description: description}),
+ )
+}
+
+func TestParamDescriptionCompactorTruncatesUnicodeSafely(t *testing.T) {
+ description := strings.Repeat("cafe\u0301 metadata ", 16)
+ api := &spec.APISpec{
+ Resources: map[string]spec.Resource{
+ "items": {
+ Endpoints: map[string]spec.Endpoint{
+ "list": {Params: []spec.Param{{Name: "expand", Type: "string", Description: description}}},
+ "search": {Params: []spec.Param{{Name: "expand", Type: "string", Description: description}}},
+ "recent": {Params: []spec.Param{{Name: "expand", Type: "string", Description: description}}},
+ },
+ },
+ },
+ }
+
+ got := NewParamDescriptionCompactor(api).Description(spec.Param{Name: "expand", Type: "string", Description: description})
+
+ assert.True(t, utf8.ValidString(got))
+ assert.LessOrEqual(t, utf8.RuneCountInString(got), sharedParamDescriptionMax)
+ assert.True(t, strings.HasSuffix(got, sharedParamDescriptionTail))
+}
diff --git a/internal/pipeline/toolsmanifest.go b/internal/pipeline/toolsmanifest.go
index 73636a63..47aff511 100644
--- a/internal/pipeline/toolsmanifest.go
+++ b/internal/pipeline/toolsmanifest.go
@@ -109,6 +109,7 @@ func WriteToolsManifest(dir string, parsed *spec.APISpec) error {
// For cookie/composed auth, only include NoAuth endpoints.
cookieOrComposed := parsed.Auth.Type == "cookie" || parsed.Auth.Type == "composed"
+ paramDescriptions := mcpdesc.NewParamDescriptionCompactorForEndpoints(manifestEndpoints(parsed, cookieOrComposed))
manifest := ToolsManifest{
APIName: parsed.Name,
@@ -159,7 +160,7 @@ func WriteToolsManifest(dir string, parsed *spec.APISpec) error {
PublicCount: public,
TotalCount: total,
})
- tool := buildManifestTool(toolName, desc, endpoint)
+ tool := buildManifestTool(toolName, desc, endpoint, paramDescriptions.Description)
manifest.Tools = append(manifest.Tools, tool)
}
@@ -181,7 +182,7 @@ func WriteToolsManifest(dir string, parsed *spec.APISpec) error {
PublicCount: public,
TotalCount: total,
})
- tool := buildManifestTool(toolName, desc, endpoint)
+ tool := buildManifestTool(toolName, desc, endpoint, paramDescriptions.Description)
manifest.Tools = append(manifest.Tools, tool)
}
}
@@ -201,7 +202,7 @@ func WriteToolsManifest(dir string, parsed *spec.APISpec) error {
// buildManifestTool creates a ManifestTool from an endpoint, classifying
// each parameter's location.
-func buildManifestTool(name, description string, ep spec.Endpoint) ManifestTool {
+func buildManifestTool(name, description string, ep spec.Endpoint, describeParam func(spec.Param) string) ManifestTool {
tool := ManifestTool{
Name: name,
Description: description,
@@ -229,7 +230,7 @@ func buildManifestTool(name, description string, ep spec.Endpoint) ManifestTool
Name: p.Name,
Type: normalizeParamType(p.Type),
Location: loc,
- Description: p.Description,
+ Description: describeParam(p),
Required: p.Required,
})
}
@@ -240,7 +241,7 @@ func buildManifestTool(name, description string, ep spec.Endpoint) ManifestTool
Name: p.Name,
Type: normalizeParamType(p.Type),
Location: "body",
- Description: p.Description,
+ Description: describeParam(p),
Required: p.Required,
})
}
@@ -259,6 +260,25 @@ func buildManifestTool(name, description string, ep spec.Endpoint) ManifestTool
return tool
}
+func manifestEndpoints(parsed *spec.APISpec, cookieOrComposed bool) []spec.Endpoint {
+ var endpoints []spec.Endpoint
+ for _, resource := range parsed.Resources {
+ for _, endpoint := range resource.Endpoints {
+ if !cookieOrComposed || endpoint.NoAuth {
+ endpoints = append(endpoints, endpoint)
+ }
+ }
+ for _, subResource := range resource.SubResources {
+ for _, endpoint := range subResource.Endpoints {
+ if !cookieOrComposed || endpoint.NoAuth {
+ endpoints = append(endpoints, endpoint)
+ }
+ }
+ }
+ }
+ return endpoints
+}
+
// normalizeAuthFormat rewrites the auth format string so that derived
// placeholders (like {token} from DUB_TOKEN) become the actual env var
// name ({DUB_TOKEN}). This way the mega MCP's runtime expansion only needs
diff --git a/internal/pipeline/toolsmanifest_test.go b/internal/pipeline/toolsmanifest_test.go
index 88499b30..69f98650 100644
--- a/internal/pipeline/toolsmanifest_test.go
+++ b/internal/pipeline/toolsmanifest_test.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"testing"
+ "github.com/mvanhorn/cli-printing-press/v3/internal/naming"
"github.com/mvanhorn/cli-printing-press/v3/internal/spec"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -286,6 +287,89 @@ func TestWriteToolsManifest_ReclassifiedPathParamKeepsPathLocation(t *testing.T)
assert.False(t, calendar.Required, "default fills in if omitted; agent may skip the param")
}
+func TestWriteToolsManifest_CompactsRepeatedParamDescriptions(t *testing.T) {
+ dir := t.TempDir()
+ sharedDescription := "Select additional nested resource fields to include in the response. Use comma-separated field names such as owner, permissions, metadata, relationships, and auditTrail; unsupported values are ignored by the upstream API."
+ parsed := &spec.APISpec{
+ Name: "test-api",
+ BaseURL: "https://api.test.com",
+ Auth: spec.AuthConfig{Type: "none"},
+ Resources: map[string]spec.Resource{
+ "Items": {
+ Endpoints: map[string]spec.Endpoint{
+ "List": {
+ Method: "GET",
+ Path: "/items",
+ Description: "List items",
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: sharedDescription}},
+ },
+ "Search": {
+ Method: "GET",
+ Path: "/items/search",
+ Description: "Search items",
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: sharedDescription}},
+ },
+ "Recent": {
+ Method: "GET",
+ Path: "/items/recent",
+ Description: "List recent items",
+ Params: []spec.Param{{Name: "expand", Type: "string", Description: sharedDescription}},
+ },
+ },
+ },
+ },
+ }
+
+ require.NoError(t, WriteToolsManifest(dir, parsed))
+
+ data, err := os.ReadFile(filepath.Join(dir, ToolsManifestFilename))
+ require.NoError(t, err)
+ assert.NotContains(t, string(data), sharedDescription,
+ "tools-manifest.json should not repeat long shared parameter descriptions verbatim")
+
+ var got ToolsManifest
+ require.NoError(t, json.Unmarshal(data, &got))
+ require.Len(t, got.Tools, 3)
+ for _, tool := range got.Tools {
+ require.Len(t, tool.Params, 1)
+ assert.Equal(t, "Select additional nested resource fields to include in the response.", tool.Params[0].Description)
+ }
+}
+
+func TestWriteToolsManifest_PreservesUniqueLongParamDescriptions(t *testing.T) {
+ dir := t.TempDir()
+ description := "Filter records by a curated vendor-specific field path, including deeply nested owner metadata, lifecycle state, and audit fields. Allowed values: owner.profile.email, lifecycle.status, audit.actor, audit.requestId."
+ parsed := &spec.APISpec{
+ Name: "test-api",
+ BaseURL: "https://api.test.com",
+ Auth: spec.AuthConfig{Type: "none"},
+ Resources: map[string]spec.Resource{
+ "Items": {
+ Endpoints: map[string]spec.Endpoint{
+ "List": {
+ Method: "GET",
+ Path: "/items",
+ Description: "List items",
+ Params: []spec.Param{{Name: "filter", Type: "string", Description: description}},
+ },
+ },
+ },
+ },
+ }
+
+ require.NoError(t, WriteToolsManifest(dir, parsed))
+
+ data, err := os.ReadFile(filepath.Join(dir, ToolsManifestFilename))
+ require.NoError(t, err)
+
+ var got ToolsManifest
+ require.NoError(t, json.Unmarshal(data, &got))
+ require.Len(t, got.Tools, 1)
+ require.Len(t, got.Tools[0].Params, 1)
+ assert.Equal(t, naming.OneLineNormalize(description), got.Tools[0].Params[0].Description)
+ assert.Contains(t, got.Tools[0].Params[0].Description, "audit.requestId")
+}
+
func TestWriteToolsManifest_AuthConfigRoundTrip(t *testing.T) {
dir := t.TempDir()
parsed := &spec.APISpec{
← 847f7c5b fix(skills): tighten publish and polish workflows (#568)
·
back to Cli Printing Press
·
feat(cli): add spec-driven tier routing (#570) 5bc73909 →