Repository navigation
feat: adopt MCP 2026 and expand the curated catalog - #284
Conversation
|
Too many files changed for review. ( Bypass the limit by tagging |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (65)
📒 Files selected for processing (237)
Disabled knowledge base sources:
WalkthroughMCP support was migrated to the official Go SDK, with typed marketplace launch and input contracts, daemon-mediated OAuth flows, secure HTTP clients, protocol negotiation, deferred discovery, durable registration persistence, updated CLI/API/UI integrations, and manifest version 2. ChangesMCP modernization
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
internal/api/core/settings_mcp_auth.go (1)
121-130: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winManual mode can return an empty callback URL.
Automatic mode validates
MCP.OAuth.RedirectURI(scheme, host, loopback), but manual mode returns the trimmed value with anilerror. Ifredirect_uriis unset,BeginMCPAuthreceivesCallbackURL: ""and the operator gets an authorization URL that cannot complete, with no actionable diagnostic. Reject the blank case here unless the settings service already enforces it.🛡️ Proposed guard
case contract.SettingsMCPAuthBeginModeManual: - return strings.TrimSpace(h.Config.MCP.OAuth.RedirectURI), nil + redirectURI := strings.TrimSpace(h.Config.MCP.OAuth.RedirectURI) + if redirectURI == "" { + return "", errors.New("settings: MCP OAuth redirect_uri is required") + } + return redirectURI, nil#!/bin/bash # Check whether BeginMCPAuth validates a blank CallbackURL, and whether a default redirect_uri exists. rg -nP --type=go -C6 'func .*BeginMCPAuth\(' internal/settings internal/daemon internal/mcp/auth rg -nP --type=go -C3 'RedirectURI' internal/config🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/core/settings_mcp_auth.go` around lines 121 - 130, Update mcpOAuthCallbackURLForMode for manual mode to reject a trimmed-empty MCP.OAuth.RedirectURI with an actionable error instead of returning it with nil error; preserve the existing non-empty manual value and automatic-mode behavior, unless an existing settings validation already guarantees this.internal/mcp/auth/types.go (1)
292-306: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validateno longer constrains the auth type.Only emptiness is checked, so any
Typevalue passes even thoughauthTypeOAuthis the single supported mode.ServerConfigFromMCPalways setsoauth, but configs assembled elsewhere would silently reach the OAuth service.#!/bin/bash # Find ServerConfig literals built outside ServerConfigFromMCP and inspect the Type they set. ast-grep run --pattern 'auth.ServerConfig{$$$}' --lang go rg -nP --type=go -C3 'ServerConfig\{' internal | rg -n 'Type:' -C3🛡️ Optional tightening
- case strings.TrimSpace(c.Type) == "": - return errors.New("mcp auth: auth type is required") + case strings.TrimSpace(c.Type) == "": + return errors.New("mcp auth: auth type is required") + case strings.TrimSpace(c.Type) != authTypeOAuth: + return fmt.Errorf("mcp auth: unsupported auth type %q", c.Type)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/auth/types.go` around lines 292 - 306, Update ServerConfig.Validate to reject any non-empty Type other than the supported authTypeOAuth value, while preserving the existing required-type, client-ID, and registration-strategy validation. Anchor the change in the Type validation case within Validate and return an appropriate validation error for unsupported types.internal/daemon/native_mcp_provider.go (1)
17-24: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winConfirm MCP call timeouts are still enforced after removing the default probe timeout option.
optionsno longer seedsmcppkg.WithTimeoutwith the configured agent probe timeout, per the line-range summary. Coding guidelines require explicit timeouts on outbound network calls; please confirmmcppkg.NewMCPCallExecutor(or its underlying transport) still enforces a bound when noWithTimeoutoption is supplied, otherwise MCP calls could rely solely on caller-supplied context deadlines.As per coding guidelines, "All outbound HTTP/network calls must use clients with explicit timeouts;
http.DefaultClientis forbidden in production paths."#!/bin/bash # Inspect the MCP call executor for a fallback/default timeout when WithTimeout is not supplied. rg -n -A5 -B5 'func NewMCPCallExecutor|func WithTimeout' internal/mcp🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/native_mcp_provider.go` around lines 17 - 24, Verify the timeout behavior of mcppkg.NewMCPCallExecutor and its underlying transport when options excludes mcppkg.WithTimeout; if no explicit fallback exists, restore or supply the configured agent probe timeout in the resolver’s options. Ensure every outbound MCP network call retains a bounded timeout without relying only on caller context deadlines.internal/daemon/daemon_mcp_serve_e2e_integration_test.go (1)
33-37: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winRegister the timeout cancellation with
t.Cleanup.
cancel()is a plaindeferin the test body, whilestartMCPServeClientregisterssession.Close()viat.Cleanup. Go runs the test function’s own deferred statements beforet.Cleanupcallbacks, so the context is canceled—and the subprocess managed byexec.CommandContextis shut down by that time. Tuckcancel()intot.Cleanupso the teardown ordering stays explicit and does not depend on the SDK’s handling of an already-terminated command.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/daemon_mcp_serve_e2e_integration_test.go` around lines 33 - 37, Update the timeout setup in the test to register cancel with t.Cleanup instead of deferring it directly, preserving cleanup ordering with startMCPServeClient’s session.Close callback.internal/mcp/auth/manager.go (1)
446-458: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftStore I/O now runs while
m.muis held.
tokenMatchesServerDefinitionperforms a registration-store lookup (andpersistReplacementTokenAtGenerationa write), both executed under the manager mutex taken at Line 446. Previously the match was a pure fingerprint comparison. Every other manager operation (Begin,Invalidate, claims) now blocks on that database round-trip. Consider validating the revision/generation under the lock, releasing it, then performing the lookup and persistence with a re-check.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/auth/manager.go` around lines 446 - 458, The token replacement flow currently holds m.mu during store lookup and persistence; update it to validate the session revision/generation under the lock, release the lock before tokenMatchesServerDefinition and persistReplacementTokenAtGeneration, then re-acquire and re-check the session state before committing so concurrent invalidation cannot succeed.internal/daemon/settings_mcp_runtime.go (1)
96-112: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winConfirm the MCP probe still has a bounded timeout.
The default 5s probe timeout and the
WithProbeTimeout-style option were removed fromnewMCPProbeExecutor, andMCPServerRuntimeStatusnow depends entirely on the caller'sctxfor cancellation. If this is invoked with an undeadlined context (assettings_test.gonow does withcontext.Background()), a slow/unresponsive remote MCP server could block the probe indefinitely.Please confirm
mcppkg.NewMCPCallExecutor/CallExecutorenforces its own internal timeout independent of this removed option; if not, this call site should wrapctxwith an explicit timeout before invokingListToolsWithProtocol.#!/bin/bash # Inspect executor construction for any default/internal timeout enforcement. ast-grep outline internal/mcp --items all --type function --match 'NewMCPCallExecutor|ListToolsWithProtocol' rg -n --type=go -C5 'func NewMCPCallExecutor' internal/mcp rg -n --type=go -C5 'func.*ListToolsWithProtocol' internal/mcp🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/settings_mcp_runtime.go` around lines 96 - 112, Verify whether mcppkg.NewMCPCallExecutor or CallExecutor provides an internal probe timeout independent of the caller context. If it does not, update MCPServerRuntimeStatus to derive a context with the established 5-second timeout before invoking ListToolsWithProtocol, while preserving cancellation from the caller; do not rely on newMCPProbeExecutor alone to enforce the bound.internal/settings/service_test.go (1)
3201-3243: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winSubtest name contradicts its assertions.
Should garbage collect a superseded install-owned refasserts the opposite: after reinstalling withVaultRef: ownedRef, the ref must still bePresent(Lines 3239-3242). As written it is a near duplicate of the following retention test, and no case actually covers collecting a superseded owned ref. Rename to reflect retention, or make it assert real collection so it fails if GC behavior regresses.♻️ Rename to match the asserted behavior
- t.Run("Should garbage collect a superseded install-owned ref", func(t *testing.T) { + t.Run("Should retain an owned ref supplied explicitly as a vault_ref", func(t *testing.T) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/settings/service_test.go` around lines 3201 - 3243, Rename the subtest beginning with “Should garbage collect a superseded install-owned ref” to describe retention of the superseded install-owned reference, matching its assertion that metadata remains Present after replacement with VaultRef: ownedRef. Leave the test behavior and assertions unchanged.
🟡 Minor comments (20)
web/src/systems/settings/stores/mcp-authorize-store.ts-244-248 (1)
244-248: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winInvalid redirect URL is a silent no-op.
When an operator pastes something that isn't an absolute MCP redirect URL, the reducer returns without transitioning and without recording an error, so the dialog appears frozen. Consider transitioning to the completion-failure state (or having the caller validate and surface a message) so the error state is explicit.
As per coding guidelines, "Handle loading, error, and empty states explicitly".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/systems/settings/stores/mcp-authorize-store.ts` around lines 244 - 248, The manualRedirectSubmitted handler silently returns for invalid redirect URLs, leaving the dialog in its current phase. Update this handler to explicitly transition to the completion-failure/error state and record the invalid URL error, while preserving the existing exchange flow for valid absolute MCP redirect URLs.Source: Coding guidelines
web/src/systems/settings/stores/mcp-authorize-store.ts-191-214 (1)
191-214: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
authorizeRequestedforwards un-normalized approved scopes.
scopeEscalationRequestedstoresnormalizeMCPAuthScopes(event.approvedScopes)(Line 160), but hereevent.scopeApprovalis validated throughhasExplicitScopeApproval— which normalizes only for the length check — and then stored and sent tobeginas-is. The two entry points therefore put differently-shaped scope lists on the wire.🐛 Normalize once before storing and forwarding
authorizeRequested: (context, event, enqueue) => { if (context.phase !== "idle") return; if (event.scopeApproval && !hasExplicitScopeApproval(event.scopeApproval)) return; + const scopeApproval = event.scopeApproval + ? { + approveScopeEscalation: event.scopeApproval.approveScopeEscalation, + approvedScopes: normalizeMCPAuthScopes(event.scopeApproval.approvedScopes), + } + : undefined; const attemptId = context.attemptId + 1; enqueueBegin( event.begin, enqueue, attemptId, event.server, event.filter, event.mode, - event.scopeApproval + scopeApproval ); return { phase: "beginning", attemptId, dismissOnConfirmation: event.dismissOnConfirmation, server: event.server, filter: event.filter, prior: event.prior, mode: event.mode, - scopeApproval: event.scopeApproval, + scopeApproval, }; },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/systems/settings/stores/mcp-authorize-store.ts` around lines 191 - 214, Update authorizeRequested to normalize event.scopeApproval once after validation, then use the normalized value for both enqueueBegin and the returned state’s scopeApproval field. Preserve undefined when no scope approval is provided and keep the existing idle and validation guards unchanged.internal/marketplace/source_test.go-290-294 (1)
290-294: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winVerify the diagnostic for an older manifest.
A
manifest_version: 1feed is asserted to reportclient too old, which inverts the actual situation (the feed is older than the client). IfUnsupportedManifestVersionErroremits one message for both directions, operators reading logs for a stale feed will be told to upgrade the client.#!/bin/bash # Inspect the unsupported-version error construction and its wording. rg -nP --type=go -C8 'UnsupportedManifestVersionError' internal/marketplace🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/marketplace/source_test.go` around lines 290 - 294, Correct the expected diagnostic in the “Should reject a v1 manifest” test to describe an older feed rather than an outdated client, and inspect UnsupportedManifestVersionError to ensure its wording distinguishes manifest versions older than the client from versions newer than the client.internal/api/core/settings_mcp_install.go-62-62 (1)
62-62: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winConfirm the settings service resolves an omitted
scope.
scopebecame optional and the required-scope check was dropped, soMCPCatalogInstallRequest.Scopecan now be""whileworkspace_idmay still be present. UnlessInstallMCPCatalogdefaults and re-validates the scope/workspace pairing, an install can land in an unclassified scope — the changed tests always send an explicit scope, so this path is uncovered.#!/bin/bash # Verify scope defaulting/validation for catalog installs. rg -nP --type=go -C10 'func .*InstallMCPCatalog\(' internal/settings internal/daemon rg -nP --type=go -C4 'Scope\s*==\s*""' internal/settingsAs per coding guidelines, "workspace data must be classified by scope and checked for cross-workspace leakage".
Also applies to: 76-80, 97-103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/core/settings_mcp_install.go` at line 62, Update InstallMCPCatalog and its MCPCatalogInstallRequest handling to resolve an omitted Scope before installation. Default the scope using the existing settings behavior, then validate the scope/workspace_id pairing so workspace data is classified and cross-workspace installs are rejected; preserve explicit-scope behavior and apply the same handling to the related request construction and validation paths.internal/mcp/executor.go-156-190 (1)
156-190: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAuthorization errors are masked once the call deadline elapses.
Lines 157-166 overwrite
errwithctx.Err()/DeadlineExceededbefore the scope/authorization checks, so a 401 orinsufficient_scopethat arrives as the deadline expires is reported as a timeout andReasonMCPAuthRequirednever reaches the caller — the re-authorize path is lost. Classify auth first, then fall back to context normalization.🐛 Proposed fix
func normalizeMCPErrorWithContext(ctx context.Context, id toolspkg.ToolID, err error, discovery bool) error { + var scopeErr *InsufficientScopeError + if errors.As(err, &scopeErr) { + return toolspkg.NewToolError( + toolspkg.ErrorCodeUnavailable, + id, + scopeErr.Error(), + scopeErr, + toolspkg.ReasonMCPAuthRequired, + ) + } + if errors.Is(err, ErrAuthorizationRequired) { + return toolspkg.NewToolError( + toolspkg.ErrorCodeUnavailable, + id, + ErrAuthorizationRequired.Error(), + ErrAuthorizationRequired, + toolspkg.ReasonMCPAuthRequired, + ) + } if ctxErr := ctx.Err(); ctxErr != nil { err = ctxErr } else if deadline, hasDeadline := ctx.Deadline(); hasDeadline && !time.Now().Before(deadline) { err = context.DeadlineExceeded } else { var networkError net.Error if errors.As(err, &networkError) && networkError.Timeout() { err = context.DeadlineExceeded } } - var scopeErr *InsufficientScopeError - if errors.As(err, &scopeErr) { - return toolspkg.NewToolError( - toolspkg.ErrorCodeUnavailable, - id, - scopeErr.Error(), - scopeErr, - toolspkg.ReasonMCPAuthRequired, - ) - } - if errors.Is(err, ErrAuthorizationRequired) { - return toolspkg.NewToolError( - toolspkg.ErrorCodeUnavailable, - id, - ErrAuthorizationRequired.Error(), - ErrAuthorizationRequired, - toolspkg.ReasonMCPAuthRequired, - ) - } if discovery {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/executor.go` around lines 156 - 190, Update normalizeMCPErrorWithContext to perform the InsufficientScopeError and ErrAuthorizationRequired checks before overwriting err with context or timeout errors, preserving the existing auth-specific ToolError responses; only apply context/deadline normalization when the original error is not authorization-related, then retain the existing discovery and general error handling.internal/mcp/executor.go-192-216 (1)
192-216: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winBound
tools/listpagination and dereference safely.
ListToolsResulthas noGetTTLMs()method here, so reading the TTL should go through the SDK’s actual cache/TTL field if required. Pagination also has no cap: keep adding pages until the context deadline ifNextCursornever stabilizes or terminates, so enforce a page limit. A nilresultguard could avoid a panic if the SDK/transport returnsnilbefore an error.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/executor.go` around lines 192 - 216, The listMCPTools pagination flow must use the SDK-supported cache/TTL field instead of calling the unavailable result.GetTTLMs() method, guard against a nil result before dereferencing Tools or NextCursor, and enforce a finite maximum page count while preserving repeated-cursor detection and context/error propagation.internal/config/provider_test.go-1283-1305 (1)
1283-1305: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winWrap the two validation cases in
t.Run("Should ...")subtests.This test asserts two distinct behaviors (valid pre-registered OAuth passes; missing
ClientIDfails) sequentially in one function, unlike the table-drivent.Runpattern used by the neighboring tests in this file.As per path instructions, "MUST use t.Run("Should...") pattern for ALL test cases."
♻️ Suggested refactor
func TestMCPServerValidateSupportsRemotePreRegisteredOAuth(t *testing.T) { t.Parallel() server := MCPServer{ Name: "linear", Transport: MCPServerTransportHTTP, URL: "https://mcp.example/mcp", Auth: MCPAuthConfig{ Registration: MCPAuthRegistrationPreRegistered, IssuerURL: "https://auth.example", ClientID: "client-1", Scopes: []string{"read", "write"}, }, } t.Run("Should validate a pre-registered remote OAuth config", func(t *testing.T) { t.Parallel() if err := server.Validate("mcp_servers[0]"); err != nil { t.Fatalf("Validate(remote OAuth) error = %v", err) } }) t.Run("Should reject a missing pre-registered client ID", func(t *testing.T) { t.Parallel() invalid := server invalid.Auth.ClientID = "" if err := invalid.Validate("mcp_servers[0]"); err == nil { t.Fatal("Validate(missing pre-registered client ID) error = nil, want validation failure") } }) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/config/provider_test.go` around lines 1283 - 1305, Refactor TestMCPServerValidateSupportsRemotePreRegisteredOAuth to place each validation case in its own t.Run subtest using the “Should ...” naming pattern: one subtest for accepting valid pre-registered remote OAuth and one for rejecting a missing ClientID. Keep the cases independent by validating a copied server with the empty ClientID, and preserve parallel execution where appropriate.Source: Path instructions
internal/mcp/auth/fingerprint.go-113-121 (1)
113-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTrim both sides of the resource URL comparison.
The left side is trimmed but
firstNonEmpty(cfg.ResourceURL, cfg.RemoteURL)is compared raw. If the config value carries surrounding whitespace, the binding check fails and forces an unnecessary re-authorization. Please confirmfirstNonEmptytrims; otherwise wrap the right side instrings.TrimSpace.#!/bin/bash ast-grep run --pattern 'func firstNonEmpty($$$) $_ { $$$ }' --lang go internal/mcp/auth🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/auth/fingerprint.go` around lines 113 - 121, Update the resource URL comparison in the registration/token binding logic to trim the result of firstNonEmpty(cfg.ResourceURL, cfg.RemoteURL) with strings.TrimSpace, unless firstNonEmpty already guarantees trimming. Preserve the existing fallback selection and other comparison checks.internal/mcp/auth/sdk_adapter.go-199-211 (1)
199-211: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winWell-known path candidate retains the resource URL's query and fragment.
atEndpointcopies the resource URL wholesale, sohttps://host/mcp?tenant=a#xyields/.well-known/oauth-protected-resource/mcp?tenant=a#x. The root candidate already clears both; do the same here to build a conformant URL and avoid forwarding resource query params to the metadata endpoint.🐛 Proposed fix
atEndpoint := *resource atEndpoint.Path = "/.well-known/oauth-protected-resource/" + strings.TrimLeft( resource.Path, "/", ) + atEndpoint.RawQuery = "" + atEndpoint.Fragment = ""🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/auth/sdk_adapter.go` around lines 199 - 211, Update the atEndpoint construction in the resource URL candidate logic to clear the copied query and fragment fields before appending its metadata URL. Preserve the existing host, scheme, and transformed path behavior while ensuring the well-known candidate never forwards resource query parameters or fragments.web/src/systems/marketplace/components/marketplace-detail-extension-actions.tsx-185-185 (1)
185-185: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
formatMarketplaceVersion's empty-input contract is unspecified, and one caller assumesundefined. The consent dialog falls back with??, which only triggers fornull/undefined, while the sibling branch at Line 87 of the same file andmarketplace-card.tsxtreat the result as possibly empty-string. The new test table never pins the empty case, so neither assumption is enforced.
web/src/systems/marketplace/components/marketplace-detail-extension-actions.tsx#L185-L185: switch the fallback to||so an empty formatted version still renders "the newer release".web/src/systems/marketplace/components/__tests__/marketplace-ui.test.ts#L42-L49: add["", ""](and a whitespace-only input) to the table so the empty-input return value becomes a pinned contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/systems/marketplace/components/marketplace-detail-extension-actions.tsx` at line 185, Update marketplace-detail-extension-actions.tsx at lines 185-185 to use a truthiness fallback for formatMarketplaceVersion so empty results render “the newer release”; in marketplace-ui.test.ts lines 42-49, add table cases for empty and whitespace-only inputs, pinning the formatter’s empty-input contract to return an empty string.internal/mcp/hosted_proxy_test.go-151-155 (1)
151-155: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDouble
session.Close()can fail the test spuriously.The session is closed explicitly at Line 205 and again by the
t.Cleanupregistered at Line 151, which callst.Errorfon any error. Rely on the cleanup only (or make the cleanup tolerant of an already-closed session). Same for the provider-protocol test:cancel()at Line 324 is not deferred, so an earlyt.Fatalfleaks the context.🧪 Proposed change
cancel() - if err := session.Close(); err != nil { - t.Fatalf("session.Close() error = %v", err) - } select {Also applies to: 204-207
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/hosted_proxy_test.go` around lines 151 - 155, Remove the explicit session.Close call in the hosted-proxy test so the t.Cleanup callback is the sole owner of session cleanup and cannot report a duplicate-close error. Apply the same cleanup ownership to the provider-protocol test, ensuring cancel() is deferred immediately after context creation so early t.Fatalf paths still release the context.internal/mcp/serve_test.go-68-76 (1)
68-76: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssertion no longer fails on a missing schema.
json.Marshal(nil)returnsnull, which passes bothlen(schema) == 0andjson.Valid, so the "schema-backed tool" invariant can no longer fail. Assert presence explicitly.💚 Proposed fix
schema, err := json.Marshal(tool.InputSchema) if err != nil { t.Fatalf("json.Marshal(tool %q input schema) error = %v", tool.Name, err) } - if len(schema) == 0 || !json.Valid(schema) { + if len(schema) == 0 || string(schema) == "null" || !json.Valid(schema) { t.Fatalf("tool %q schema = %s, want valid JSON", tool.Name, schema) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/serve_test.go` around lines 68 - 76, Update the schema validation loop in the tool test to explicitly reject a missing or nil tool.InputSchema before marshaling it. Preserve the existing JSON marshaling and validity checks for present schemas so schema-backed tools must provide valid, non-null JSON.internal/settings/mcp_catalog_input.go-185-191 (1)
185-191: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winSecret inputs ignore
input.Binding.Type.Both branches bind unconditionally to env (
setMCPSecretEnvRef/secrets.SecretEnv). A catalog entry declaring a secret with aurl_query(or any non-env) binding is accepted and silently mis-bound, unlike the plain-input path which rejects unsupported binding types. Reject non-envbindings here (or explicitly support them).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/settings/mcp_catalog_input.go` around lines 185 - 191, Update the secret-input handling around setMCPSecretEnvRef and secrets.SecretEnv so it validates input.Binding.Type before binding; reject any type other than env, matching the plain-input path’s unsupported-binding behavior, while preserving both existing env-reference and literal-secret handling for valid env bindings.internal/store/globaldb/global_db_mcp_auth_atomic_test.go-136-138 (1)
136-138: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winError-path assertions in the globaldb MCP auth suite only check non-nil. All four sites force a specific failure (SQLite trigger or record validation) but assert only
err == nil, so an unrelated error — validation rejecting the record before the trigger fires, a target mismatch, a closed handle — still passes and hides the rollback/validation regression under test.global_db_test.goalready asserts the forced message; apply the same discipline here.
internal/store/globaldb/global_db_mcp_auth_atomic_test.go#L136-L138: assert the error containsforced MCP registration update failure.internal/store/globaldb/global_db_mcp_auth_atomic_test.go#L194-L196: assert the error containsforced MCP registration secret cleanup failure.internal/store/globaldb/global_db_mcp_auth_atomic_test.go#L235-L237: assert the error containsforced authorization cleanup failure.internal/store/globaldb/global_db_mcp_auth_test.go#L417-L432: assert the validation error containsaccess token and client URI must be set together.As per path instructions, tests MUST have specific error assertions (ErrorContains, ErrorAs) and must avoid "weak assertions like assert.Error(t, err) without message validation".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/globaldb/global_db_mcp_auth_atomic_test.go` around lines 136 - 138, Strengthen the error assertions in internal/store/globaldb/global_db_mcp_auth_atomic_test.go at lines 136-138, 194-196, and 235-237 by requiring errors containing “forced MCP registration update failure”, “forced MCP registration secret cleanup failure”, and “forced authorization cleanup failure” respectively; update internal/store/globaldb/global_db_mcp_auth_test.go lines 417-432 to require the validation error containing “access token and client URI must be set together”. Use specific message validation such as ErrorContains rather than non-nil-only assertions, preserving the existing test scenarios around SaveMCPAuthRegistration and authorization cleanup.Source: Path instructions
internal/testutil/mcpfixture/fixture_test.go-185-204 (1)
185-204: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winStrengthen error assertions in
TestFixtureValidation.All three checks only assert
err != nil, never validating the error message/content. This can't detect whether the correct validation branch fired (e.g., "unsupported profile" vs. a completely different failure) if the underlying logic regresses.As per path instructions, tests "MUST have specific error assertions (ErrorContains, ErrorAs)" and must avoid "Weak assertions like assert.Error(t, err) without message validation."
🧪 Proposed fix using message-specific assertions
- if _, err := New(Profile("unsupported")); err == nil { - t.Fatal("New(unsupported profile) error = nil, want validation error") + if _, err := New(Profile("unsupported")); err == nil || !strings.Contains(err.Error(), "profile") { + t.Fatalf("New(unsupported profile) error = %v, want validation error mentioning profile", err) } fixture := MustNew(ProfileModern2026) - if err := fixture.RunStdio(t.Context(), nil, nil); err == nil { - t.Fatal("RunStdio(nil input) error = nil, want validation error") + if err := fixture.RunStdio(t.Context(), nil, nil); err == nil || !strings.Contains(err.Error(), "input") { + t.Fatalf("RunStdio(nil input) error = %v, want validation error mentioning input", err) } input := io.NopCloser(strings.NewReader("")) - if err := fixture.RunStdio(t.Context(), input, nil); err == nil { - t.Fatal("RunStdio(nil output) error = nil, want validation error") + if err := fixture.RunStdio(t.Context(), input, nil); err == nil || !strings.Contains(err.Error(), "output") { + t.Fatalf("RunStdio(nil output) error = %v, want validation error mentioning output", err) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/testutil/mcpfixture/fixture_test.go` around lines 185 - 204, Strengthen the three error checks in TestFixtureValidation by asserting each error contains the expected validation message, not merely that an error exists. Add message-specific assertions for New(Profile("unsupported")), RunStdio with nil input, and RunStdio with nil output, while preserving the existing close-error check.Source: Path instructions
internal/testutil/mcpfixture/oauth_test.go-226-234 (1)
226-234: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert the redirect status before trusting
Location.If
/authorizerejects the request (e.g. missingresourcematch),Locationis empty andurl.Parse("")succeeds, so the assertion fails with a misleadingissmismatch instead of the real cause.🐛 Proposed fix
response := performRequest(t, client, http.MethodGet, authorizeURL.String(), nil) defer closeResponse(t, response) + if got, want := response.StatusCode, http.StatusFound; got != want { + t.Fatalf("mix-up authorize status = %d, want %d", got, want) + } callbackURL, err := url.Parse(response.Header.Get("Location"))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/testutil/mcpfixture/oauth_test.go` around lines 226 - 234, In the authorization test around performRequest, assert that the response has the expected redirect status before parsing or validating its Location header. Keep the existing callbackURL parsing and issuer assertion afterward so rejected /authorize responses fail with the actual status-related cause rather than an empty URL mismatch.internal/testutil/mcpfixture/oauth.go-326-338 (1)
326-338: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
continueleaves a nil map in the cloned slice.On a marshal/unmarshal failure the pre-sized element stays
nil, soRequests()silently returns a hole instead of the recorded registration, which makes any dependent assertion fail with a confusing nil dereference. Prefer appending only successful clones (or failing the clone loudly).🐛 Proposed fix
func cloneRegistrationSlice(values []map[string]any) []map[string]any { - cloned := make([]map[string]any, len(values)) - for index, value := range values { + cloned := make([]map[string]any, 0, len(values)) + for _, value := range values { data, err := json.Marshal(value) if err != nil { continue } - if err := json.Unmarshal(data, &cloned[index]); err != nil { + var entry map[string]any + if err := json.Unmarshal(data, &entry); err != nil { continue } + cloned = append(cloned, entry) } return cloned }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/testutil/mcpfixture/oauth.go` around lines 326 - 338, Update cloneRegistrationSlice so marshal or unmarshal failures do not leave nil entries in the returned slice; build the result by appending only successfully cloned maps, or propagate the failure explicitly. Preserve the order of successful registrations and ensure Requests() cannot receive holes from this helper.packages/site/components/marketplace/marketplace-entry-card.tsx-79-86 (1)
79-86: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMissing separator between the launch pill and the input summary.
When
inputsis present the row renderspill,inputs,separator,scope— the input summary has no separator preceding it, so it visually merges with the pill, while the same markup renders correctly wheninputsis null.🐛 Proposed fix
- {inputs ? <span>{inputs}</span> : null} + {inputs ? ( + <> + <MetaSeparator /> + <span>{inputs}</span> + </> + ) : null} <MetaSeparator />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/site/components/marketplace/marketplace-entry-card.tsx` around lines 79 - 86, Update the JSX around the launch type Pill and conditional inputs summary in the marketplace entry card to render a MetaSeparator between them when inputs is present. Preserve the existing separator before the default-scope text and avoid adding an extra separator when inputs is absent.packages/site/lib/marketplace-catalog.ts-319-325 (1)
319-325: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winMessage references a launch type that no longer exists.
launch.typeisnpm | uvx | docker | remote; "stdio" is not one of them, so the validation error is confusing for catalog authors.🐛 Proposed fix
- message: "env inputs are only allowed for stdio launch", + message: "env inputs are only allowed for local launch (npm, uvx, docker)",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/site/lib/marketplace-catalog.ts` around lines 319 - 325, Update the validation message in the env-binding check within the catalog validation flow to reference the supported launch types accurately instead of “stdio”; preserve the existing condition and issue path.web/src/systems/settings/components/mcp-authorize-dialog.tsx-294-294 (1)
294-294: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winValidate the trimmed value to match submission behavior.
submitManualaccepts padded input (the hook trims before exchanging, peruse-mcp-authorize.test.tsxline 243), but the enablement check validates the raw textarea value. A pasted URL with a leading/trailing newline would keep the button disabled with no explanation.🛠️ Proposed fix
- disabled={!isAbsoluteMCPRedirectURL(manualValue) || exchanging} + disabled={!isAbsoluteMCPRedirectURL(manualValue.trim()) || exchanging}Please confirm whether
isAbsoluteMCPRedirectURLalready trims internally; if it does, this is a no-op.#!/bin/bash fd -t f 'mcp-authorize-model.ts' web/src --exec cat -n🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/systems/settings/components/mcp-authorize-dialog.tsx` at line 294, Update the manual submission button’s disabled condition near submitManual to validate the same trimmed manualValue that submission uses, while preserving the existing exchanging check. First verify whether isAbsoluteMCPRedirectURL trims internally; if it does, leave this condition unchanged, otherwise pass the trimmed value for padded URLs to enable correctly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: eccdddc5-3fd7-469a-a7b8-261fde51eea5
⛔ Files ignored due to path filters (65)
bun.lockis excluded by!**/*.lock,!**/*.lockcatalog/extensions.jsonis excluded by!**/*.jsoncatalog/mcp.jsonis excluded by!**/*.jsoncatalog/skills.jsonis excluded by!**/*.jsondocs/qa/charters/CH-mcp-protocol-interop.mdis excluded by!**/*.mddocs/qa/journeys/J-mcp-authorize-repair.mdis excluded by!**/*.mddocs/qa/journeys/J-mcp-protocol-interop.mdis excluded by!**/*.mddocs/qa/reports/2026-07-30-mcp-2026-catalog-v2-plan.mdis excluded by!**/*.mddocs/qa/reports/2026-07-30-mcp-2026-catalog-v2.mdis excluded by!**/*.mddocs/qa/scenarios/ET-046.mdis excluded by!**/*.mddocs/qa/scenarios/ET-047.mdis excluded by!**/*.mddocs/qa/scenarios/ET-048.mdis excluded by!**/*.mddocs/qa/scenarios/ET-049.mdis excluded by!**/*.mddocs/qa/scenarios/ET-api-marketplace-namespace.mdis excluded by!**/*.mddocs/qa/scenarios/ET-api-mcp-catalog-install.mdis excluded by!**/*.mddocs/qa/scenarios/ET-api-mcp-oauth-endpoints.mdis excluded by!**/*.mddocs/qa/scenarios/ET-cli-marketplace-info.mdis excluded by!**/*.mddocs/qa/scenarios/ET-cli-marketplace-refresh.mdis excluded by!**/*.mddocs/qa/scenarios/ET-cli-marketplace-search.mdis excluded by!**/*.mddocs/qa/scenarios/ET-cli-mcp-auth-manual-exchange.mdis excluded by!**/*.mddocs/qa/scenarios/ET-cli-mcp-authorize.mdis excluded by!**/*.mddocs/qa/scenarios/ET-cli-mcp-install.mdis excluded by!**/*.mddocs/qa/scenarios/ET-compozy-native-tool-invocation.mdis excluded by!**/*.mddocs/qa/scenarios/ET-compozy-official-skill-discovery.mdis excluded by!**/*.mddocs/qa/scenarios/ET-mcp-protocol-interop.mdis excluded by!**/*.mddocs/qa/scenarios/ET-site-marketplace-catalog.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-marketplace-installed-management.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-marketplace-mcp-authorize-installed.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-mcp-authorize-manual.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-mcp-authorize.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-mcp-guided-install.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-mcp-remote-editor.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-mcp-status-matrix.mdis excluded by!**/*.mddocs/qa/scenarios/ET-workspace-host-api-mcp.mdis excluded by!**/*.mddocs/qa/scenarios/MS-029.mdis excluded by!**/*.mddocs/qa/scenarios/MS-web-mcp-editor-simple-advanced.mdis excluded by!**/*.mdgo.sumis excluded by!**/*.sum,!**/*.suminternal/marketplace/testdata/catalog/missing-digest/extensions.jsonis excluded by!**/*.jsoninternal/marketplace/testdata/catalog/missing-digest/mcp.jsonis excluded by!**/*.jsoninternal/marketplace/testdata/catalog/missing-digest/skills.jsonis excluded by!**/*.jsoninternal/marketplace/testdata/catalog/valid/extensions.jsonis excluded by!**/*.jsoninternal/marketplace/testdata/catalog/valid/mcp.jsonis excluded by!**/*.jsoninternal/marketplace/testdata/catalog/valid/skills.jsonis excluded by!**/*.jsoninternal/store/globaldb/schema/migrations/atlas.sumis excluded by!**/*.sum,!**/*.sumopenapi/compozy.jsonis excluded by!**/*.jsonpackages/site/content/docs/cli/mcp/auth/login.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/mcp/install.mdxis excluded by!**/*.mdxpackages/site/content/docs/configuration/config-toml.mdxis excluded by!**/*.mdxpackages/site/content/docs/configuration/lifecycle-matrix.mdxis excluded by!**/*.mdxpackages/site/content/docs/configuration/mcp-json.mdxis excluded by!**/*.mdxpackages/site/content/docs/marketplace/index.mdxis excluded by!**/*.mdxpackages/site/content/docs/marketplace/mcp-catalog.mdxis excluded by!**/*.mdxpackages/site/content/docs/marketplace/meta.jsonis excluded by!**/*.jsonpackages/site/public/.well-known/mcp-client.jsonis excluded by!**/*.jsonskills/compozy/references/tools-and-skills.mdis excluded by!**/*.mdweb/e2e/__tests__/marketplace.spec.tsis excluded by!web/e2e/**web/e2e/fixtures/__tests__/runtime.test.tsis excluded by!**/fixtures/**,!web/e2e/**web/e2e/fixtures/marketplace-server.tsis excluded by!**/fixtures/**,!web/e2e/**web/e2e/fixtures/runtime-seed.tsis excluded by!**/fixtures/**,!web/e2e/**web/package.jsonis excluded by!**/*.jsonweb/src/generated/compozy-openapi.d.tsis excluded by!**/generated/**,!**/generated/**,!**/*.d.tsweb/src/systems/marketplace/components/stories/marketplace-overlays.stories.tsxis excluded by!**/*.stories.tsxweb/src/systems/settings/components/stories/mcp-authorize-dialog.stories.tsxis excluded by!**/*.stories.tsxweb/src/systems/settings/components/stories/mcp-server-editor.stories.tsxis excluded by!**/*.stories.tsxweb/src/systems/settings/routes/mcp-servers.stories.tsxis excluded by!**/*.stories.tsx
📒 Files selected for processing (231)
go.modinternal/acp/client_test.gointernal/api/contract/marketplace.gointernal/api/contract/mcp_catalog_install.gointernal/api/contract/settings_collection_payloads.gointernal/api/contract/settings_mcp_auth.gointernal/api/core/agent_contracts_test.gointernal/api/core/marketplace_detail.gointernal/api/core/marketplace_installed_detail.gointernal/api/core/marketplace_list.gointernal/api/core/marketplace_test.gointernal/api/core/settings_collection_requests.gointernal/api/core/settings_mcp_auth.gointernal/api/core/settings_mcp_conversion.gointernal/api/core/settings_mcp_install.gointernal/api/core/settings_test.gointernal/api/httpapi/handlers_test.gointernal/api/httpapi/helpers_test.gointernal/api/httpapi/routes.gointernal/api/spec/schema_customizers.gointernal/api/spec/settings_mcp_auth.gointernal/api/spec/settings_mcp_schema.gointernal/api/spec/settings_test.gointernal/api/udsapi/handlers_test.gointernal/api/udsapi/helpers_test.gointernal/api/udsapi/hosted_mcp.gointernal/api/udsapi/routes.gointernal/api/udsapi/transport_parity_integration_test.gointernal/cli/client_mcp.gointernal/cli/client_mcp_test.gointernal/cli/helpers_test.gointernal/cli/mcp_auth.gointernal/cli/mcp_auth_flow.gointernal/cli/mcp_auth_output.gointernal/cli/mcp_auth_test.gointernal/cli/mcp_install.gointernal/cli/mcp_install_input.gointernal/cli/mcp_install_output.gointernal/cli/mcp_install_test.gointernal/cli/mcp_serve.gointernal/cli/mcp_serve_test.gointernal/config/agent_resource_test.gointernal/config/agent_test.gointernal/config/config_extensions_sandbox.gointernal/config/config_refac_test.gointernal/config/config_test.gointernal/config/config_validation.gointernal/config/defaults.gointernal/config/lifecycle/lifecycle.gointernal/config/lifecycle/lifecycle_test.gointernal/config/mcp.gointernal/config/mcp_resource.gointernal/config/mcp_server_validation.gointernal/config/mcpjson_test.gointernal/config/merge.gointernal/config/merge_features.gointernal/config/merge_mcp.gointernal/config/merge_overlay.gointernal/config/provider.gointernal/config/provider_resolved_validation.gointernal/config/provider_test.gointernal/config/provider_validation.gointernal/daemon/daemon_mcp_serve_e2e_integration_test.gointernal/daemon/daemon_memory_e2e_integration_test.gointernal/daemon/daemon_mock_agents_integration_test.gointernal/daemon/marketplace_test.gointernal/daemon/mcp_dead_entity_integration_test.gointernal/daemon/native_extension_tool_provider.gointernal/daemon/native_mcp_provider.gointernal/daemon/settings.gointernal/daemon/settings_mcp_auth.gointernal/daemon/settings_mcp_runtime.gointernal/daemon/settings_runtime_dependencies.gointernal/daemon/settings_test.gointernal/daemon/tool_approval_grants_integration_test.gointernal/daemon/tool_mcp_resources_clone_test.gointernal/daemon/tool_mcp_resources_encoding.gointernal/marketplace/entry_mcp.gointernal/marketplace/entry_mcp_input.gointernal/marketplace/entry_mcp_launch.gointernal/marketplace/projection.gointernal/marketplace/service_integration_test.gointernal/marketplace/source.gointernal/marketplace/source_test.gointernal/marketplace/store_test.gointernal/marketplace/types.gointernal/mcp/auth/client_auth.gointernal/mcp/auth/dynamic_registration.gointernal/mcp/auth/exchange_input.gointernal/mcp/auth/fingerprint.gointernal/mcp/auth/manager.gointernal/mcp/auth/manager_test.gointernal/mcp/auth/metadata.gointernal/mcp/auth/metadata_test.gointernal/mcp/auth/pre_registered.gointernal/mcp/auth/sdk_adapter.gointernal/mcp/auth/sdk_adapter_test.gointernal/mcp/auth/service.gointernal/mcp/auth/service_direct_endpoint_test.gointernal/mcp/auth/service_expires_in_test.gointernal/mcp/auth/service_options.gointernal/mcp/auth/service_test.gointernal/mcp/auth/service_token_lifecycle.gointernal/mcp/auth/step_up.gointernal/mcp/auth/types.gointernal/mcp/authorization_required.gointernal/mcp/executor.gointernal/mcp/executor_auth.gointernal/mcp/executor_cache.gointernal/mcp/executor_client.gointernal/mcp/executor_setup.gointernal/mcp/executor_test.gointernal/mcp/hosted.gointernal/mcp/hosted_binding.gointernal/mcp/hosted_bootstrap_projection.gointernal/mcp/hosted_proxy.gointernal/mcp/hosted_proxy_result.gointernal/mcp/hosted_proxy_result_test.gointernal/mcp/hosted_proxy_test.gointernal/mcp/hosted_records.gointernal/mcp/hosted_test.gointernal/mcp/insufficient_scope.gointernal/mcp/protocol.gointernal/mcp/securehttp/body.gointernal/mcp/securehttp/client.gointernal/mcp/securehttp/client_test.gointernal/mcp/securehttp/policy.gointernal/mcp/securehttp/transport.gointernal/mcp/serve.gointernal/mcp/serve_http.gointernal/mcp/serve_projection.gointernal/mcp/serve_test.gointernal/mcp/tool_descriptor.gointernal/mcp/unsupported_capability.gointernal/settings/collection_hook_mcp_map.gointernal/settings/collection_provider_write.gointernal/settings/config_reload_diff.gointernal/settings/mcp_auth.gointernal/settings/mcp_catalog_input.gointernal/settings/mcp_catalog_install.gointernal/settings/mcp_collection_probes.gointernal/settings/mcp_mutation.gointernal/settings/mcp_runtime_status_test.gointernal/settings/mcp_target_test.gointernal/settings/models_mcp.gointernal/settings/service.gointernal/settings/service_contract.gointernal/settings/service_integration_test.gointernal/settings/service_test.gointernal/store/globaldb/global_db_marketplace_catalog_test.gointernal/store/globaldb/global_db_mcp_auth_atomic_test.gointernal/store/globaldb/global_db_mcp_auth_test.gointernal/store/globaldb/global_db_mcp_oauth_registration.gointernal/store/globaldb/global_db_test.gointernal/store/globaldb/global_db_workspace.gointernal/store/globaldb/queries/vault_mcp.sqlinternal/store/globaldb/schema/definitions/35_mcp.sqlinternal/store/globaldb/schema/migrations/00029_schema.sqlinternal/store/globaldb/schema/migrations/00030_marketplace_catalog_manifest_v2.sqlinternal/store/globaldb/schema/migrations/00031_schema.sqlinternal/store/globaldb/sqlcgen/models.gointernal/store/globaldb/sqlcgen/vault_mcp.sql.gointernal/testutil/mcpfixture/fixture.gointernal/testutil/mcpfixture/fixture_test.gointernal/testutil/mcpfixture/http.gointernal/testutil/mcpfixture/oauth.gointernal/testutil/mcpfixture/oauth_test.gointernal/testutil/mcpfixture/profile.gointernal/tools/dispatch.gointernal/tools/mcp_discovery.gointernal/tools/registry.gointernal/tools/registry_index.gointernal/tools/registry_session_projection.gointernal/tools/registry_test.gointernal/vault/mcp_refs.gointernal/vault/types_test.gopackages/site/app/marketplace/[kind]/page.tsxpackages/site/components/marketplace/marketplace-entry-card.tsxpackages/site/components/marketplace/marketplace-entry-detail.tsxpackages/site/components/site/__tests__/sidebar-compact-tree.test.tsxpackages/site/lib/__tests__/marketplace-catalog.test.tsxpackages/site/lib/marketplace-catalog-validation.tspackages/site/lib/marketplace-catalog.tspackages/site/vitest.config.tsweb/src/lib/__tests__/settings-api-contract.test.tsweb/src/systems/agent/__tests__/types.test.tsweb/src/systems/agent/routes/agent-detail-story-fixtures.tsweb/src/systems/marketplace/adapters/__tests__/marketplace-api.test.tsweb/src/systems/marketplace/components/__tests__/marketplace-action-controller.test.tsxweb/src/systems/marketplace/components/__tests__/marketplace-components.test.tsxweb/src/systems/marketplace/components/__tests__/marketplace-detail-manage.test.tsxweb/src/systems/marketplace/components/__tests__/marketplace-ui.test.tsweb/src/systems/marketplace/components/__tests__/mcp-install-dialog-store.test.tsweb/src/systems/marketplace/components/index.tsweb/src/systems/marketplace/components/marketplace-card.tsxweb/src/systems/marketplace/components/marketplace-detail-extension-actions.tsxweb/src/systems/marketplace/components/marketplace-detail-mcp-manage.tsxweb/src/systems/marketplace/components/marketplace-detail-meta.tsxweb/src/systems/marketplace/components/marketplace-detail.tsxweb/src/systems/marketplace/components/marketplace-entry-actions.tsxweb/src/systems/marketplace/components/marketplace-installed-card.tsxweb/src/systems/marketplace/components/marketplace-ui.tsweb/src/systems/marketplace/components/mcp-install-dialog.tsxweb/src/systems/marketplace/components/mcp-install-model.tsweb/src/systems/marketplace/components/use-marketplace-action-controller.tsxweb/src/systems/marketplace/components/use-mcp-install-dialog.tsweb/src/systems/marketplace/lib/__tests__/mcp-installed-status.test.tsweb/src/systems/marketplace/mocks/fixtures.tsweb/src/systems/marketplace/mocks/handlers.tsweb/src/systems/settings/adapters/__tests__/settings-mcp-auth-api.test.tsweb/src/systems/settings/components/__tests__/mcp-authorize-dialog.test.tsxweb/src/systems/settings/components/__tests__/mcp-editor-secret-presence.test.tsxweb/src/systems/settings/components/__tests__/mcp-server-editor.test.tsxweb/src/systems/settings/components/mcp-authorize-dialog.tsxweb/src/systems/settings/components/mcp-editor-connection-section.tsxweb/src/systems/settings/components/mcp-editor-oauth-section.tsxweb/src/systems/settings/components/mcp-server-editor.tsxweb/src/systems/settings/hooks/__tests__/use-mcp-authorize.test.tsxweb/src/systems/settings/hooks/__tests__/use-settings-mutations.test.tsxweb/src/systems/settings/hooks/use-mcp-authorize.tsweb/src/systems/settings/hooks/use-settings-mutations.tsweb/src/systems/settings/index.tsweb/src/systems/settings/lib/__tests__/mcp-editor-model.test.tsweb/src/systems/settings/lib/__tests__/mcp-status-view-model.test.tsweb/src/systems/settings/lib/mcp-authorize-model.tsweb/src/systems/settings/lib/mcp-editor-model.tsweb/src/systems/settings/lib/mcp-status-view-model.tsweb/src/systems/settings/mocks/__tests__/handlers.test.tsweb/src/systems/settings/mocks/fixtures.tsweb/src/systems/settings/stores/__tests__/mcp-authorize-store.test.tsweb/src/systems/settings/stores/mcp-authorize-store.ts
💤 Files with no reviewable changes (9)
- internal/api/core/marketplace_installed_detail.go
- internal/acp/client_test.go
- web/src/systems/settings/index.ts
- internal/mcp/auth/service_expires_in_test.go
- internal/mcp/auth/service_test.go
- internal/mcp/auth/metadata_test.go
- web/src/systems/settings/components/tests/mcp-editor-secret-presence.test.tsx
- internal/cli/mcp_serve.go
- internal/mcp/auth/service_direct_endpoint_test.go
CodeRabbit resolution report — PR #284All 43 normalized findings from
Additional CI blocker fixed: the |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (7)
web/src/systems/settings/stores/mcp-authorize-store.ts (2)
158-176: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winSame silent-drop pattern in
scopeEscalationRequested.Shares the same root cause as the
authorizeRequestedhandler above (line 165): an invalidscopeApproval(empty normalized scopes) causes a silent no-op instead of an explicit failure state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/systems/settings/stores/mcp-authorize-store.ts` around lines 158 - 176, The scopeEscalationRequested handler currently drops invalid normalized approvals silently. Replace the hasExplicitScopeApproval early return with the same explicit failure-state transition used by authorizeRequested, while preserving the existing reviewing_scopes transition for valid approvals and the idle-phase guard.
191-220: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winSilent no-op when
scopeApprovalfailshasExplicitScopeApproval.If
event.scopeApprovalis provided but invalid (e.g.approveScopeEscalation: false, or all scopes normalize to empty),authorizeRequestedreturns without any state transition (line 199) — the UI stays in"idle"with no error, unlike the newly addedmanualRedirectSubmittedhandling which surfaces an explicitfailedstate for an analogous invalid-input case. Worth confirming whetheruse-mcp-authorize.tscan ever dispatch such a malformed event; if so, consider surfacing afailedtransition here too for consistency and debuggability.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/systems/settings/stores/mcp-authorize-store.ts` around lines 191 - 220, Update authorizeRequested to handle invalid provided scopeApproval instead of silently returning when hasExplicitScopeApproval fails. Transition to the existing failed state with an appropriate error, matching the manualRedirectSubmitted invalid-input handling, while preserving the current beginning transition for valid approvals and the idle behavior when no scopeApproval is provided.internal/settings/mcp_catalog_input.go (1)
155-203: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing validation for empty
input.Binding.Namein secret-input binding.
bindingName(line 172) is used directly as an env key forsetMCPSecretEnvRef(195) andsecrets.SecretEnv(201) without checking it's non-empty. A malformed catalog manifest with a blank binding name would silently create an invalid empty-key environment binding, the same class of manifest-defect issue already fixed forurl_queryon the URL side.🛡️ Proposed guard
bindingName := strings.TrimSpace(input.Binding.Name) + if bindingName == "" { + return validationError(fmt.Errorf( + "settings: catalog secret input %q has an empty binding name", + id, + )) + } if !supplied {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/settings/mcp_catalog_input.go` around lines 155 - 203, Validate the trimmed bindingName in applyCatalogSecretInput before using it as an environment key. Return the existing validationError pattern with a clear catalog secret-input binding-name error when it is empty, while preserving the current vault-reference and SecretEnv assignment flows for valid names.internal/api/core/settings_mcp_auth.go (1)
126-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider validating the manual redirect URI, not just its presence.
Automatic mode parses the configured redirect URI and enforces scheme/host/loopback, while manual mode returns the raw trimmed value. A malformed configured value (e.g. missing scheme) would flow into the authorization request and fail later with an opaque provider error. Reusing the same parse/validation keeps both modes consistent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/core/settings_mcp_auth.go` around lines 126 - 130, The manual redirect URI path in the MCP OAuth settings validation must validate the trimmed value using the same parsing and scheme/host/loopback checks as automatic mode, rather than returning it immediately. Reuse the existing redirect URI validation logic and return its validation error before returning the URI.internal/mcp/auth/manager.go (1)
446-465: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract the duplicated revision check.
The lock/check/unlock block appears twice verbatim; a small
func (m *Manager) revisionMatches(targetKey string, revision uint64) boolhelper would remove the duplication and the manual unlock paths. The final persist relies on the generation fence rather than the released lock, which is consistent with the mutation-race tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/auth/manager.go` around lines 446 - 465, The revision validation in the surrounding Manager method is duplicated before and after token matching. Add a Manager.revisionMatches helper that locks internally, compares the target revision, and returns the result, then replace both manual lock/check/unlock blocks with this helper while preserving ErrLoginSessionStale handling and the existing generation-fenced persistReplacementTokenAtGeneration call.internal/testutil/mcpfixture/oauth.go (1)
326-340: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSilently dropping un-clonable entries can distort snapshot counts.
Tests assert on
len(fixture.Requests().Registration); a skipped entry would make the snapshot shorter than what the server actually recorded, turning a fixture bug into a confusing count mismatch. Since these maps come fromjson.Unmarshal, a failure here is effectively impossible — so preserving a shallow copy (or panicking) is more honest thancontinue.♻️ Suggested handling
func cloneRegistrationSlice(values []map[string]any) []map[string]any { cloned := make([]map[string]any, 0, len(values)) for _, value := range values { data, err := json.Marshal(value) if err != nil { - continue + cloned = append(cloned, value) + continue } var entry map[string]any if err := json.Unmarshal(data, &entry); err != nil { - continue + cloned = append(cloned, value) + continue } cloned = append(cloned, entry) } return cloned }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/testutil/mcpfixture/oauth.go` around lines 326 - 340, Update cloneRegistrationSlice so entries that cannot be JSON-cloned are not silently omitted, preserving the original slice length and snapshot counts. Replace the continue-based failure handling in the function with an honest fallback such as preserving a shallow copy or panicking, while retaining the existing deep-clone behavior for successful entries.internal/mcp/executor_test.go (1)
881-921: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPrefer an atomic counter for
calls.
calls++runs on the server goroutine while line 919 reads it from the test goroutine; ordering relies on the transport's happens-before edges rather than explicit synchronization.internal/mcp/auth/sdk_adapter_test.goalready usesatomic.Int32for the same pattern — matching it removes any-raceambiguity for free.♻️ Suggested change
- calls := 0 + var calls atomic.Int32 server.AddReceivingMiddleware(func(next mcpsdk.MethodHandler) mcpsdk.MethodHandler { return func(ctx context.Context, method string, request mcpsdk.Request) (mcpsdk.Result, error) { if method == "tools/list" { - calls++ - return &mcpsdk.ListToolsResult{NextCursor: fmt.Sprintf("page-%d", calls)}, nil + return &mcpsdk.ListToolsResult{ + NextCursor: fmt.Sprintf("page-%d", calls.Add(1)), + }, nil } return next(ctx, method, request) } })- if got, want := calls, maxMCPToolListPages; got != want { + if got, want := int(calls.Load()), maxMCPToolListPages; got != want { t.Fatalf("tools/list calls = %d, want %d", got, want) }As per coding guidelines, "Go tests must use
Should …subtest names, default tot.Parallel, ... run with race detection".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/executor_test.go` around lines 881 - 921, Replace the unsynchronized calls counter in the paginated tools test with an atomic counter, following the atomic.Int32 pattern used in internal/mcp/auth/sdk_adapter_test.go. Update the increment in the tools/list middleware and the assertion comparing the count with maxMCPToolListPages to use atomic operations, preserving the existing pagination behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/mcp/auth/manager_test.go`:
- Around line 399-418: Update managerTestStore.GetMCPAuthRegistration to consume
registrationStarted under s.mu and clear the field before releasing the mutex,
then close only the captured channel outside the lock. Preserve the existing
release synchronization and registration lookup behavior while ensuring repeated
calls cannot close the same channel twice.
In `@internal/mcp/auth/sdk_adapter.go`:
- Around line 170-174: Remove the no-op deferred branch around
drainAndCloseResponseBody in the response handling flow, or explicitly handle
the returned drainErr by logging or annotating it; do not silently discard the
error. Update the defer block associated with resp.Body and metadataURL so its
intent is clear and every error is handled.
In `@internal/vault/mcp_refs.go`:
- Around line 41-43: Update NormalizeRef and the MCP reference validation around
the OAuth-subtree guard to normalize each non-protected path segment before
comparing the guarded prefix, while preserving case-sensitive comparison after
normalization. Ensure uppercase user input such as OAuth is rejected before
canonical storage or resolution, while protected segments remain unchanged.
---
Nitpick comments:
In `@internal/api/core/settings_mcp_auth.go`:
- Around line 126-130: The manual redirect URI path in the MCP OAuth settings
validation must validate the trimmed value using the same parsing and
scheme/host/loopback checks as automatic mode, rather than returning it
immediately. Reuse the existing redirect URI validation logic and return its
validation error before returning the URI.
In `@internal/mcp/auth/manager.go`:
- Around line 446-465: The revision validation in the surrounding Manager method
is duplicated before and after token matching. Add a Manager.revisionMatches
helper that locks internally, compares the target revision, and returns the
result, then replace both manual lock/check/unlock blocks with this helper while
preserving ErrLoginSessionStale handling and the existing generation-fenced
persistReplacementTokenAtGeneration call.
In `@internal/mcp/executor_test.go`:
- Around line 881-921: Replace the unsynchronized calls counter in the paginated
tools test with an atomic counter, following the atomic.Int32 pattern used in
internal/mcp/auth/sdk_adapter_test.go. Update the increment in the tools/list
middleware and the assertion comparing the count with maxMCPToolListPages to use
atomic operations, preserving the existing pagination behavior.
In `@internal/settings/mcp_catalog_input.go`:
- Around line 155-203: Validate the trimmed bindingName in
applyCatalogSecretInput before using it as an environment key. Return the
existing validationError pattern with a clear catalog secret-input binding-name
error when it is empty, while preserving the current vault-reference and
SecretEnv assignment flows for valid names.
In `@internal/testutil/mcpfixture/oauth.go`:
- Around line 326-340: Update cloneRegistrationSlice so entries that cannot be
JSON-cloned are not silently omitted, preserving the original slice length and
snapshot counts. Replace the continue-based failure handling in the function
with an honest fallback such as preserving a shallow copy or panicking, while
retaining the existing deep-clone behavior for successful entries.
In `@web/src/systems/settings/stores/mcp-authorize-store.ts`:
- Around line 158-176: The scopeEscalationRequested handler currently drops
invalid normalized approvals silently. Replace the hasExplicitScopeApproval
early return with the same explicit failure-state transition used by
authorizeRequested, while preserving the existing reviewing_scopes transition
for valid approvals and the idle-phase guard.
- Around line 191-220: Update authorizeRequested to handle invalid provided
scopeApproval instead of silently returning when hasExplicitScopeApproval fails.
Transition to the existing failed state with an appropriate error, matching the
manualRedirectSubmitted invalid-input handling, while preserving the current
beginning transition for valid approvals and the idle behavior when no
scopeApproval is provided.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8cab60c7-dcd4-4b26-afbe-dd0f54d7ca66
📒 Files selected for processing (52)
internal/api/core/marketplace_detail.gointernal/api/core/settings_mcp_auth.gointernal/api/core/settings_test.gointernal/cli/mcp_auth_flow.gointernal/cli/mcp_auth_test.gointernal/config/config_test.gointernal/config/provider_test.gointernal/daemon/daemon_mcp_serve_e2e_integration_test.gointernal/marketplace/entry_mcp_input.gointernal/marketplace/errors.gointernal/marketplace/source_test.gointernal/mcp/auth/client_auth.gointernal/mcp/auth/manager.gointernal/mcp/auth/manager_test.gointernal/mcp/auth/sdk_adapter.gointernal/mcp/auth/sdk_adapter_test.gointernal/mcp/auth/service_options.gointernal/mcp/auth/types.gointernal/mcp/auth/types_test.gointernal/mcp/executor.gointernal/mcp/executor_setup.gointernal/mcp/executor_test.gointernal/mcp/executor_tools.gointernal/mcp/hosted_proxy_test.gointernal/mcp/hosted_records.gointernal/mcp/hosted_test.gointernal/mcp/protocol.gointernal/mcp/serve_test.gointernal/settings/mcp_catalog_input.gointernal/settings/service_test.gointernal/store/globaldb/global_db_mcp_auth_atomic_test.gointernal/store/globaldb/global_db_mcp_auth_test.gointernal/testutil/mcpfixture/fixture_test.gointernal/testutil/mcpfixture/oauth.gointernal/testutil/mcpfixture/oauth_test.gointernal/tools/dispatch.gointernal/tools/registry_index.gointernal/tools/registry_session_projection.gointernal/tools/registry_test.gointernal/vault/mcp_refs.gointernal/vault/types_test.gomagefiles/command.gomagefiles/gotest_lane_test.gomagefiles/test.gopackages/site/components/marketplace/marketplace-entry-card.tsxpackages/site/lib/__tests__/marketplace-catalog.test.tsxpackages/site/lib/marketplace-catalog-validation.tspackages/site/lib/marketplace-catalog.tsweb/src/systems/marketplace/components/__tests__/marketplace-components.test.tsxweb/src/systems/marketplace/components/mcp-install-dialog.tsxweb/src/systems/settings/stores/__tests__/mcp-authorize-store.test.tsweb/src/systems/settings/stores/mcp-authorize-store.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/site/lib/marketplace-catalog.ts
Migrate the runtime to the official Go SDK, hard-cut the marketplace manifest to v2, and add durable OAuth registration and token lifecycle. Populate the curated Wave 1/2 catalog and expose typed install, authorization, and status flows across CLI, HTTP, UDS, Web, site, and the official Compozy skill, with production-like QA evidence. Preserve the public protocol allowlist while adapting the private hosted proxy for the Codex legacy handshake and non-deferred native projection. BREAKING CHANGE: MCP catalogs now require manifest_version 2 and public MCP transport no longer accepts SSE.
Summary
This PR upgrades Compozy's MCP stack to the official 2026 protocol family and turns the marketplace feed from a single-entry demo into an installable, typed catalog for high-value vendor servers.
mark3labs/mcp-gowith the stable officialgithub.com/modelcontextprotocol/go-sdkv1.7.0.2026-07-28and2025-11-25, while keeping the older Codex handshake isolated to the private hosted sidecar compatibility boundary.What changed
MCP runtime and protocol
OAuth, credentials, and persistence
00031: scoped OAuth registrations.00032: invalidate regenerable manifest-v1 catalog cache state.00033: persisttoken_endpoint_auth_method.00029_schema.sqland00030_schema.sqlbyte-for-byte.Curated marketplace catalog
The manifest-v2 feed now contains:
Stdio launches use exact package/image versions, credential prompts contain metadata only, and hosted entries use only authentication fields supported by the runtime schema.
The implementation deliberately omits entries that could not be represented honestly through the current install contract:
Public and agent-manageable surfaces
packages/site/content/docs/configuration/mcp-json.mdx, marketplace documentation, and the bundled official Compozy skill.Test and QA coverage
compozy__network_channel_createnative tool call.Breaking changes
manifest_version: 2; manifest v1 is rejected.mark3labs/mcp-goto the official MCP Go SDK.CodeRabbit and React Doctor remediation
sdk/gorace tests inheritedCGO_ENABLED=0instead of the hermetic race environment.sync.Once.serverkey.100/100, with zero errors and zero warnings.Remediation and integration commits:
e772c2ff: initial 43-finding CodeRabbit remediation.b017a6cb: migration-boundary alignment after rebasing onto PR feat: select and switch runtime per session prompt #283.d02293ee: three incremental CodeRabbit findings.7c533934: React Doctor warning and duplicate Vitest configuration.Compozy Impact Audit
compozy__*IDs and tool schemas remain the source of truth; capability checks and hosted-proxy tests cover availability and dispatch.Release notes
Features
Fixes and security
Documentation
Verification
Verification completed before the final commit/publish instruction; no new global local gate or visual-evidence pass was run during the shipping, review, or rebase steps.
go run ./cmd/compozy-catalog validate ./catalog.globaldbsuite, targeted migration race tests, sidebar suite, codegen consistency, and migration identity checks.internal/mcp/authandinternal/vaultsuites under the race detector.blocked-verifybecause it requires a human authorization grant.Full report:
docs/qa/reports/2026-07-30-mcp-2026-catalog-v2.md.Local QA artifacts are retained and indexed by the report; absolute lab paths are intentionally not presented as reviewer-clickable repository evidence.
Known reviewer notes
stdio/http/ssewording:docs/qa/charters/CH-mcp-authorize-repair-truth.mdanddocs/qa/scenarios/MS-029.md. They do not define the current runtime contract.mcp.oauth.redirect_uri; a consolidated canonicalconfig.tomlreference remains a documentation follow-up.@assistant-ui/*version change from the preceding spec commit; there is no corresponding package manifest change in this implementation commit.Summary by CodeRabbit
New Features
Bug Fixes