Repository navigation
Conversation
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
edgeorgie
force-pushed
the
fix/mcp-tool-grouping-read-only
branch
from
October 9, 2026 22:19
30e1790 to
e18b35d
Compare
edgeorgie
marked this pull request as ready for review
October 9, 2026 23:07
|
Risk: No findings This increment only relabels the MCP tool grouping headers and adds hover disclaimers on both the web and desktop approval UIs, to address the earlier finding that an unverified server self-attestation was presented as a trust label. No new inputs, data flows, or logic were introduced, and the fix correctly reframes the readOnlyHint group as a server-reported, unverifiable claim on both surfaces. Sentinel reviewed |
A malicious MCP server can set readOnlyHint:true on a destructive tool (e.g. delete_issue, write_file). The UI previously rendered this as an unqualified 'Read-only tools' label, letting a user approve a write/delete-capable tool under a false-safe label. This contradicted this PR's own design principle that untrusted hints should only ever escalate restriction, never loosen it. Backend grouping logic (get_is_read_only) was already correct and unchanged - this is a labeling/trust-framing fix only, in both the web and desktop surfaces: - web: relabel the group header to 'Server-reported read-only' with a tooltip explaining the claim is unverified - desktop: same relabel, added an optional labelTooltip prop to ToolGroup so the same clarification renders on hover Addresses parameter.ai Sentinel's inline review finding on ServerDetailPanel.tsx:220.
This branch has not been deployed
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.
Problem
A user approving an MCP server's tools sees every tool in one flat list, with no way to tell which ones only read data versus which ones can write or delete it. On a server with a dozen-plus tools,
delete_issuerenders identically tolist_issues, so approval becomes a guess rather than an informed decision.The information needed to fix this already existed and was already being thrown away. MCP servers declare a
readOnlyHintannotation on each tool per the MCP spec, the backend already persists it unmodified onMCPServerInstallationTool.annotations, but no serializer field ever surfaced it, so neither frontend could read it.A prior attempt, #76411, fixed this for the web app only (
products/mcp_store) and was closed for inactivity before merging, not on its merits. The issue's own title and screenshots are specifically about PostHog Desktop, which #76411 never touched. Desktop has its own independent tool-list UI (ServerDetailView.tsx) backed by the same backend API, so closing #76236 properly means extending #76411's approach to both surfaces, not just reviving it for web.Closes #76236
Changes
Shared contract, two independent consumers. The backend exposes one new boolean field; both frontends read it and build their own grouped UI from it. Nothing is shared between the two UI implementations beyond the field itself, which is why both needed a change:
flowchart LR A{{"MCP server"}} -->|"declares readOnlyHint"| B["MCPServerInstallationTool.annotations<br/>(stored unmodified, pre-existing)"] B --> C["MCPServerInstallationToolSerializer<br/>get_is_read_only()"] C -->|"is_read_only: bool"| D[/"Tool list API response"/] D --> E["ServerDetailPanel.tsx<br/>web: products/mcp_store"] D --> F["ServerDetailView.tsx<br/>desktop: products/desktop"] classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff; classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff; classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000; classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000; class A phYellow; class B phGray; class C phBlue; class D phRed; class E,F phBlue;MCPServerInstallationToolSerializeraddsis_read_only, aSerializerMethodFieldderived fromannotations.readOnlyHint.is_read_only: false(write/delete-capable), never totrue. The annotation is server-reported and unverified, so an absent or ambiguous hint has to assume the more dangerous capability. This mirrors the existing policy layer's stance elsewhere in this serializer: an untrusted signal can only tighten restriction, never loosen it.read_only_hint) instead of deriving a normalizedis_read_onlyboolean. Exposing the raw annotation key would leak MCP-spec vocabulary into the API contract and push the trust decision (what counts as "read-only enough to group separately") into every consumer. Deriving one boolean in the serializer keeps that decision in one place and gives both frontends an API contract that's already a plain boolean, not a hint they'd each have to interpret the same way independently.products/mcp_store/frontend/scene/ServerDetailPanel.tsx): the flat tool list becomes twoLemonCollapsepanels, "Read-only tools" and "Write or delete tools", each with a count badge. Both default open.products/desktop): addedgroupToolsByReadOnlynext to the existing tool-derivation helpers intoolDerivation.ts;ServerDetailView.tsxrenders the same two labeled groups through a newToolGroupcomponent instead of one flat list.How did you test this code?
Test rationale: This is a display/grouping change with no new state machine, so coverage sits at the three points where behavior actually branches: the serializer's
is_read_onlyderivation, and the newgroupToolsByReadOnlyhelper on desktop. Web already has component-level logic coverage inmcpStoreLogic.test.ts; its fixture was updated for the new field rather than adding a parallel test, matching the existing pattern there.test_list_tools_reports_is_read_only_from_annotationstoproducts/mcp_store/backend/test/test_api.py, coveringreadOnlyHint: true,readOnlyHint: false, and missing annotations.products/desktop/packages/core/src/mcp-servers/toolDerivation.test.tsforgroupToolsByReadOnly(splits correctly, treats missingis_read_onlyas write/delete).pnpm --filter @posthog/corevitest suite fortoolDerivation— 6/6 passing.pnpm --filter=@posthog/frontend exec jest --testPathPattern=products/mcp_store/frontend— 16 suites / 133 tests passing.oxlinton the changed web files — clean.python3 -m py_compileon the changed backend files — clean.tsgotypecheck (OOMs on this machine regardless of this change) and Desktop's@posthog/uivitest suite (pre-existing failures from an unbuilt@posthog/sharedpackage, unrelated to this change) — confirmed zero new TypeScript errors via a before/after error diff instead.Release status
Docs update
None — internal UI grouping only, no user-facing docs reference tool list layout.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Sonnet 4.5
/improving-drf-endpoints(serializer field getshelp_textand@extend_schema_field, matching the checklist for aSerializerMethodField),/writing-tests(new coverage extends the nearest existing test file/block rather than adding a parallel suite), and/writing-pr-descriptions(shaped this body and its Mermaid diagram)./adopting-generated-api-typeswas checked but doesn't apply: this PR adds a serializer field and regeneratesapi.schemas.ts/generated.tsthrough the normal pipeline, it does not touch any manualapi.get/ApiRequestcall sites.gh pr list --search "mcp tool group"andgh api search/issues?q=repo:PostHog/posthog+is:pr+76236before starting. Found feat(mcp-store): group installed tools by read and write/delete #76411 (web-only, closed for inactivity, not on merits, discussed above) and no other open attempt.ServerDetailView.tsxUI composition itself, which has no existing test file for the component it was changed in (confirmed by searching). No test was added here to avoid introducing a first-of-its-kind UI test inconsistent with the file's current state.