feat(profile): discovery enforcement — hidden_by_profile, policy-aware search (Spec 108-b) - #1390
Merged
Merged
Conversation
Spec 108 (Profiles v3) FR-011: SearchToolsScoped's predicate only sees a hit's server name, so a caller with a tool-level policy could have a policy-excluded hit displace an admitted one from a limited window, and hidden_by_profile would undercount. SearchToolsAdmitted extends the index API with a hit-level predicate over the canonical (server, tool) identity, returning a three-way Admission (Admit / RejectScope / RejectPolicy) instead of a bool: the limit applies to admitted hits only, and RejectPolicy (never RejectScope) is counted over the full exhaustive match set. The index stores no annotations and gains none; the predicate's tier classification comes from the caller.
PR 108-b (profile-discovery-enforcement), FR-010/011/012/015/019. retrieve_tools now filters by tool policy before the ranked cut via SearchToolsAdmitted, gains hidden_by_profile (non-legacy profiles only, SC-003 byte parity preserved) and profile (url/session sources only, never a pin). describe_tool (indexed and direct surfaces) answers an excluded tool with the same uniform not-found response a nonexistent tool gets. /mcp/all tools/list drops policy-excluded tools at list time only, leaving the call-time filter chain untouched so a future call-time gate (108-d) still reaches the handler. indexedToolVisible gets the identical policy check as post-cut defence in depth. resolveActiveProfileWithSource extends the existing resolver to also report which precedence tier (pin/url/session) produced the effective profile, needed to decide whether the new `profile` field may name the slug at all. Prompts are unaffected by design (FR-019): filterAggregatedPromptsForAuth consults only server scope, never the tool policy. Policy exercised only under the FR-009a test override until 108-d lifts the rollout gate.
A read_cache page produced under one tool policy must be refused once that policy changes, even when the profile's server set (already covered pre-108) is unchanged. Authorization gains PolicyFingerprint (the effective profile's compiled policy fingerprint, hex-encoded) and ToolTierGeneration, both folded into the existing digest so the frame header's stamp mechanics are unchanged. Supervisor.ToolTierGeneration is a coarse, whole-fleet counter bumped once per publishDiscoveredTools call, so an out-of-band tool re-annotation invalidates cached pages with no config edit at all.
T001-T003 (shared setup, already done in 108-a) and T016-T026 (profile-discovery-enforcement) checked off. Regenerated ROADMAP.md.
Picks up 281b5c8 (Spec 109 health status vocabulary), unrelated to this PR's files.
1. SearchToolsAdmitted stopped counting hiddenByPolicy as soon as the page filled with `limit` admitted hits, undercounting any policy-excluded match ranked below the cut. FR-011 requires the count over the full match set; the page now stops growing at `limit` but the scan keeps counting to the end. 2. collectQuarantinedToolMatches (the quarantined/pending "locked" entry pass) filtered only by server scope, so a tool that was BOTH policy-excluded and quarantined/pending/changed was named in the disabled[] array and counted as "locked" instead of staying silently invisible like every other excluded tool. Post-filtered through the same CompiledPolicy.Decide the rest of discovery uses. 3. describe_tool's policy re-check ran after the quarantine/pending/ callability gates, so an excluded-and-locked tool answered its lock reason (confirming existence) instead of the uniform not-found the contract requires. Moved right after the scope check, before every gate that would otherwise narrow an admitted tool's shape. Each fix ships with a regression test reproducing the exact scenario.
… nit) Round 2 review confirmed the round-1 fixes are correct, but flagged that the doc comment still claimed identical exhaustive paging to SearchToolsScoped -- the two now differ in stop rule on purpose (this method never early-returns, so hiddenByPolicy counts the full match set). Comment-only change; behaviour unchanged.
Deploying mcpproxy-docs with
|
| Latest commit: |
632cb1b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://55ccb927.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://108-b-profile-discovery-enfo.mcpproxy-docs.pages.dev |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 36392819630 --repo smart-mcp-proxy/mcpproxy-go
|
- retrieve_tools' usageStatEligible (include_stats filter) now applies the same Spec 108 policy gate its sibling resolvers (toolVisibleToSession, indexedToolVisible) already had, so a policy-hidden tool can no longer be named in usage_summary.top_tools for a profile-scoped caller even though an earlier caller's usage created a fleet-wide stats record for it (F1). - TestScopeOracleV3_HiddenByProfileIdenticalAcrossFixtures now probes with the hidden servers' own sentinel tool names, not only queries that can never match them, so the differential oracle can actually detect a scope leak instead of comparing two empty result sets (F2). - TestRetrieveTools_ScopeLatency_ProfileV3VsLegacy now rebuilds the two per-profile Bleve indexes from the shared one before measuring, so the benchmark scans real hits and actually pays the policy path's per-hit cost instead of two no-op searches against empty ForProfile stores (F3). Each fix is verified test-first: the new/extended assertions reproduce red against the pre-fix code and pass after it.
Contributor
There was a problem hiding this comment.
Approved: exact-head live QA passed and ZCode merge-only review returned PASS for 632cb1b.
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.
Summary
Implements profile-aware discovery for MCP and REST surfaces. Search now filters excluded tools before applying result limits and reports accurate hidden-by-profile counts; describe_tool and direct list routes hide excluded tools uniformly. Cache reuse is keyed by the profile policy so a policy change cannot reuse an admitted result from the prior policy.
The fail-closed rollout gate remains active until execution enforcement lands in Spec 108-d.
Spec and findings
specs/108-profiles-v3/— FR-009a, FR-010–012, FR-015 and FR-015a.internal/server/testdata/retrieve_tools_profile_v3.golden.json.Verification
go build ./...internal/cache,internal/index,internal/runtime/supervisor, andinternal/serverwith the server build tag and repository skip regex.--new-from-rev=origin/main. GitHub CI is running on the re-synced head.max_tier: read, exits 4, and printsmax_tier is not supported by this build (Profiles v3 enforcement incomplete), as required by FR-009a. Discovery-specific enforcement is covered by the PR's profile test suite; a live profile-policy session is enabled in 108-d when every execution path is enforced.Re-sync
Clean automatic merge of current main after PR #1381; no manual conflict resolution.