From 3f010b855e32e72f0b719d69064a620b528061b2 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 19:27:04 +0000 Subject: [PATCH] feat(api): declare explicit OpenAPI tags --- openapi.go | 18 ++++++++++++++++++ openapi_test.go | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/openapi.go b/openapi.go index 07ba581..670a3ef 100644 --- a/openapi.go +++ b/openapi.go @@ -349,6 +349,24 @@ func (sb *SpecBuilder) buildTags(groups []RouteGroup) []map[string]any { }) seen[name] = true } + + dg, ok := g.(DescribableGroup) + if !ok { + continue + } + + for _, rd := range dg.Describe() { + for _, tag := range rd.Tags { + if tag == "" || seen[tag] { + continue + } + tags = append(tags, map[string]any{ + "name": tag, + "description": tag + " endpoints", + }) + seen[tag] = true + } + } } return tags diff --git a/openapi_test.go b/openapi_test.go index b4b7ca9..5b55ef6 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -885,6 +885,25 @@ func TestSpecBuilder_Good_ToolBridgeIntegration(t *testing.T) { t.Fatalf("invalid JSON: %v", err) } + tags, ok := spec["tags"].([]any) + if !ok { + t.Fatalf("expected tags array, got %T", spec["tags"]) + } + expectedTags := map[string]bool{ + "system": true, + "tools": true, + "files": true, + "metrics": true, + } + for _, raw := range tags { + tag := raw.(map[string]any) + name, _ := tag["name"].(string) + delete(expectedTags, name) + } + if len(expectedTags) != 0 { + t.Fatalf("expected declared tags to include system, tools, files, and metrics, missing %v", expectedTags) + } + paths := spec["paths"].(map[string]any) // Verify POST /tools/file_read exists.