Skip to content

feat(mcp-store): marketplace with installed-server rail and per-tool approval#1747

Open
cvolzer3 wants to merge 4 commits intomainfrom
feat/refactor-mcp-store
Open

feat(mcp-store): marketplace with installed-server rail and per-tool approval#1747
cvolzer3 wants to merge 4 commits intomainfrom
feat/refactor-mcp-store

Conversation

@cvolzer3
Copy link
Copy Markdown
Contributor

@cvolzer3 cvolzer3 commented Apr 21, 2026

Screenshot 2026-04-20 at 10 12 27 PM

Summary

Rebuilds the MCP settings scene to match the new PostHog MCP store shape and the handoff prototype.

  • Adopts the split template (shared OAuth) vs custom (per-user DCR) install model: install_template/ for marketplace connects, install_custom/ for user-supplied URLs with optional client creds.
  • Adds per-tool approval UI (Approved / Requires approval / Blocked) with bulk set-all and a removed-tools toggle; backend proxy already enforces via JSON-RPC errors -32001 / -32002.
  • Restructures the section into a two-pane layout: a 256px installed-server rail with local search + status pulse dots, plus a right-hand pane that routes between marketplace / detail / add-custom.
  • Introduces a fullwidth flag on Settings categories so MCP opts out of the standard 800px centered container; every other page is unchanged.
  • Drops the dead auth_type: "none" branch in the main-process auth adapter — every installation routes through the MCP proxy.

Test plan

  • Settings → MCP servers renders the two-pane layout; other settings pages unchanged.
  • Marketplace search + category chips filter the grid; "Clear filters" resets both.
  • Rail search filters the installed list; "Active" count reflects filtered results.
  • Rail installed list is alphabetical; status pulse shows green / amber / red correctly.
  • Connecting an OAuth template opens the browser, auto-advances to the installation detail on callback, and populates tools with needs_approval.
  • Per-tool toggle cycles Approved / Requires approval / Blocked; bulk set-all works; Refresh resyncs tools.
  • Clicking an already-installed card in the marketplace routes to the installation detail (not the empty template view).
  • Adding a custom OAuth server with optional client creds completes DCR + OAuth and lands on the new installation.
  • Uninstalling from the detail view clears the rail row and returns to marketplace.
  • pnpm --filter code typecheck, pnpm lint, and pnpm --filter code test all pass.

@cvolzer3
Copy link
Copy Markdown
Contributor Author

This PR is dependent on a PR in the main /posthog repo: PostHog/posthog#55407

…proval test

- Revert apps/code/src/renderer/App.tsx to main (unrelated invite-gate
  changes and the relative-import workaround were never part of the MCP
  refactor).
- Delete filterInstallationsByCategory; the category cross-filter lives
  in the rail as a local query now, so the helper has no callers.
- Extract dispatchBulkApproval into a testable pure helper and cover
  its parallel dispatch, failure propagation, and removed-tool skip.
@cvolzer3 cvolzer3 requested a review from a team April 21, 2026 03:50
@cvolzer3 cvolzer3 marked this pull request as ready for review April 21, 2026 03:50
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 21, 2026

Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/settings/components/sections/McpServersSettings.tsx
Line: 131-142

Comment:
**Custom install doesn't navigate to the new installation**

After a successful custom server install the view is reset to `{ kind: "marketplace" }`, so the user never lands on the new installation's detail. The test plan explicitly requires: _"Adding a custom OAuth server…completes DCR + OAuth and lands on the new installation."_

The template flow can auto-advance because the `useEffect` at line 117-125 matches the returned `installationList` entry by `template_id`. Custom installations have no `template_id`, so that effect never fires.

To fix this, `installCustomMutation` in `useMcpServers` should return the new `McpServerInstallation` (the API already does — `installCustomMcpServer` resolves to `McpServerInstallation | OAuthRedirectResponse`, and after `runOAuthInstall` you re-fetch the list). One approach: have the `onSuccess` callback in `McpServersSettings` receive the installation ID from the invalidated list (or return it from the mutation) and call `setView({ kind: "detail-installation", installationId })` instead of reverting to marketplace.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/code/src/renderer/features/settings/hooks/mcpFilters.test.ts
Line: 73

Comment:
**`filterInstallationsByQuery` has no test coverage**

`mcpFilters.ts` exports three functions, but the test file only covers two of them — `filterInstallationsByQuery` is entirely untested. It is non-trivial: it searches across `display_name`, `name`, `url`, `description`, and template fields pulled from a `Map`. Given that the sibling functions each have five or more parameterised cases, and the test plan explicitly says "Rail search filters the installed list", this function deserves the same treatment.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "chore(mcp): revert stray App.tsx drift, ..." | Re-trigger Greptile

Comment on lines +131 to +142
if (view.kind === "add-custom") {
return (
<AddCustomServerForm
pending={installCustomPending}
onBack={() => setView({ kind: "marketplace" })}
onSubmit={(values) => {
installCustom(values, {
onSuccess: () => setView({ kind: "marketplace" }),
});
}}
/>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Custom install doesn't navigate to the new installation

After a successful custom server install the view is reset to { kind: "marketplace" }, so the user never lands on the new installation's detail. The test plan explicitly requires: "Adding a custom OAuth server…completes DCR + OAuth and lands on the new installation."

The template flow can auto-advance because the useEffect at line 117-125 matches the returned installationList entry by template_id. Custom installations have no template_id, so that effect never fires.

To fix this, installCustomMutation in useMcpServers should return the new McpServerInstallation (the API already does — installCustomMcpServer resolves to McpServerInstallation | OAuthRedirectResponse, and after runOAuthInstall you re-fetch the list). One approach: have the onSuccess callback in McpServersSettings receive the installation ID from the invalidated list (or return it from the mutation) and call setView({ kind: "detail-installation", installationId }) instead of reverting to marketplace.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/settings/components/sections/McpServersSettings.tsx
Line: 131-142

Comment:
**Custom install doesn't navigate to the new installation**

After a successful custom server install the view is reset to `{ kind: "marketplace" }`, so the user never lands on the new installation's detail. The test plan explicitly requires: _"Adding a custom OAuth server…completes DCR + OAuth and lands on the new installation."_

The template flow can auto-advance because the `useEffect` at line 117-125 matches the returned `installationList` entry by `template_id`. Custom installations have no `template_id`, so that effect never fires.

To fix this, `installCustomMutation` in `useMcpServers` should return the new `McpServerInstallation` (the API already does — `installCustomMcpServer` resolves to `McpServerInstallation | OAuthRedirectResponse`, and after `runOAuthInstall` you re-fetch the list). One approach: have the `onSuccess` callback in `McpServersSettings` receive the installation ID from the invalidated list (or return it from the mutation) and call `setView({ kind: "detail-installation", installationId })` instead of reverting to marketplace.

How can I resolve this? If you propose a fix, please make it concise.

it("returns empty when nothing matches", () => {
expect(filterServersByQuery(all, "zzz")).toEqual([]);
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 filterInstallationsByQuery has no test coverage

mcpFilters.ts exports three functions, but the test file only covers two of them — filterInstallationsByQuery is entirely untested. It is non-trivial: it searches across display_name, name, url, description, and template fields pulled from a Map. Given that the sibling functions each have five or more parameterised cases, and the test plan explicitly says "Rail search filters the installed list", this function deserves the same treatment.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/settings/hooks/mcpFilters.test.ts
Line: 73

Comment:
**`filterInstallationsByQuery` has no test coverage**

`mcpFilters.ts` exports three functions, but the test file only covers two of them — `filterInstallationsByQuery` is entirely untested. It is non-trivial: it searches across `display_name`, `name`, `url`, `description`, and template fields pulled from a `Map`. Given that the sibling functions each have five or more parameterised cases, and the test plan explicitly says "Rail search filters the installed list", this function deserves the same treatment.

How can I resolve this? If you propose a fix, please make it concise.

Adds a search input to the tools section of the MCP server detail screen
that filters the tool list by name. The search field appears when there
are more than 5 tools and includes a clear button.
@cvolzer3 cvolzer3 changed the title feat(mcp): marketplace with installed-server rail and per-tool approval feat(mcp-store): marketplace with installed-server rail and per-tool approval Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant