Conversation
…s own domain basecamp/mcp's catalog.Load groups SDK operations into MCP domain tools by their OpenAPI tag, one tag per operation. The six recording-lifecycle operations — ListRecordings, SpotlightRecording, UnspotlightRecording, TrashRecording, ArchiveRecording, UnarchiveRecording — carried the Automation tag, so adopting the SDK's native tag mapping would fold basecamp_recordings into basecamp_admin and silently drop the deliberately-designed recordings tool surface. (The account event-feed surface already got its own EventFeed tag in #898; this closes the matching gap for recordings.) Retag the six under a new Recordings tag, mirroring the Recordings service every SDK generator already emits for exactly these operations. Recording boosts stay under Boosts and the recording timesheet stays under Schedule -> Timesheets, matching the SDK service groupings. Each generator assigns services from its own split table rather than from the OpenAPI tag, so all seven SDKs regenerate byte-identical; the artifacts that copy the tag verbatim change: openapi.json, the go url-routes resource field (which now reads "Recordings" for these routes instead of the stale "Automation"), and the TypeScript vendored openapi-stripped.json. Regenerated via make smithy-build, make url-routes, and the TypeScript generator. behavior-model.json is unchanged: tags live only in the OpenAPI projection.
…e tag The recordings-into-Automation fold happened because nothing enforced that every operation carries its own tag: an operation added to the Smithy model without a matching entry in spec/overlays/tags.smithy reaches openapi.json untagged, and catalog.Load then drops it or folds it into an unrelated domain. This is an accident-class guard — anyone who can add an operation can add a tag; the check exists so they cannot do the first and forget the second. check-required-tags.rb reads the committed openapi.json — the same artifact catalog.Load consumes — and fails if any operation has zero tags or more than one. The allowlist is empty and documented: every Basecamp API operation is a catalog operation and must carry a domain tag; a genuinely tag-less operation would be added by operationId with a reason rather than by weakening the check. It fails closed on a spec that yields no operations, so a truncated openapi.json cannot pass vacuously. The smithy-verify "OpenAPI is up to date" step already proves openapi.json is regenerated from the model, so a dropped tag reaches this gate rather than hiding behind a stale file. test-check-required-tags.rb drives the check with crafted openapi documents (untagged, empty-tagged, multi-tagged, allowlisted, empty, and path-level parameters) since the live run only ever exercises the passing case. Both are wired into make check and the spec-gates CI job (bash/ruby, no toolchain).
Contributor
Sensitive Change Detection (shadow mode)This PR modifies control-plane files:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
basecamp-mcp-server builds its MCP tool catalog by grouping SDK operations by their OpenAPI tag — one tag per operation, each tag becoming one domain tool (
basecamp_recordings,basecamp_events, …). An operation with no tag of its own gets dropped or folded into an unrelated domain.Two recently-shipped, deliberately-designed surfaces were exposed to this:
AccountClient.EventFeed()service added in 0.19.0). Already fixed — Account event feed, inbox, and stream tickets: spec operations and generated clients #898 taggedPollEvents/PollInbox/CreateStreamTicketwithEventFeed. No change needed here; noted for completeness.Automation, so adopting the SDK's native tag mapping would foldbasecamp_recordingsintobasecamp_admin. This PR fixes that.What was untagged / mis-tagged
All 265 operations already carry exactly one tag in
openapi.json; the recordings gap was mis-tagging, not a literal absent tag. These six carriedAutomation:ListRecordings,SpotlightRecording,UnspotlightRecording,TrashRecording,ArchiveRecording,UnarchiveRecordingThe tags added
Retagged those six under a new
Recordingstag inspec/overlays/tags.smithy, mirroring theRecordingsservice every SDK generator already emits for exactly these ops. Recording boosts stay underBoostsand the recording timesheet stays underSchedule → Timesheets, matching the SDK service groupings — this is the coherent recordings domain, nothing more.Regeneration
make smithy-build,make url-routes, and the TypeScript generator. Because each SDK generator assigns services from its own split table (not the OpenAPI tag), all seven SDKs regenerate byte-identical — verified: ruby regen produced zero diff, andcheck-operation-assignment-parity(all 6 SDKs),check-service-inventory-parity, andkt-check-driftare all clean. The only committed artifacts that change are the three that copy the tag verbatim:openapi.json(6 operation tags:Automation→Recordings)go/pkg/basecamp/url-routes.json(theresourcefield for recording routes now readsRecordingsinstead of the staleAutomation— a latent consistency fix)typescript/src/generated/openapi-stripped.jsonbehavior-model.jsonis unchanged — tags live only in the OpenAPI projection.Require-tags guard (fail-closed) — implemented
Survey first: 0 untagged operations, 0 multi-tagged — all 265 carry exactly one tag. Because the exception set is empty, the fail-closed check is clean to add, so I implemented it rather than deferring.
scripts/check-required-tags.rbreads the committedopenapi.json(the artifactcatalog.Loadconsumes) and fails if any operation has zero tags or more than one. It's an accident-class guard: it stops a new operation added to the Smithy model without a matchingtags.smithyentry from shipping untagged. Allowlist is empty and documented — every Basecamp API operation is a catalog operation; a genuinely tag-less op would be added by operationId with a reason rather than by weakening the check. It also fails closed on a spec that yields no operations (a truncatedopenapi.jsoncan't pass vacuously).scripts/test-check-required-tags.rbdrives it with crafted specs (untagged, empty-tagged, multi-tagged, allowlisted, empty, path-level parameters) since the live run only exercises the passing case. Both are wired intomake check(check-targets) and the spec-gates CI job (bash/ruby, no toolchain). Adversarial check: stripping a tag from a copy ofopenapi.jsonmakes the gate exit 1 and name the offender.Follow-up linkage
After this merges and a new SDK release is cut, mcp-server's #140 / M2 can consume the
Recordings(andEventFeed) tags sobasecamp_recordingsandbasecamp_eventskeep their own MCP domains instead of folding intobasecamp_admin/basecamp_schedules.Summary by cubic
Retags the six recording lifecycle operations from
AutomationtoRecordingsso MCP catalog generation gives them their ownbasecamp_recordingsdomain instead of folding them intobasecamp_admin. Adds a fail-closed check that every operation inopenapi.jsoncarries exactly one tag.openapi.json, Gourl-routes.json(whose staleAutomationresource label becomesRecordings), and TypeScriptopenapi-stripped.json.Recordingsservice each SDK generator already emits; recording boosts stay underBoostsand the recording timesheet stays underSchedule.scripts/check-required-tags.rbrejects zero, multiple, or absent operations and is wired intomake checkand the spec-gates CI job, with a self-test covering the negative cases.Written for commit de86704. Summary will update on new commits.