Skip to content

Feat/selectable mcps to staging#11

Merged
DIodide merged 10 commits intostagingfrom
feat/selectable-mcps-to-staging
Mar 18, 2026
Merged

Feat/selectable mcps to staging#11
DIodide merged 10 commits intostagingfrom
feat/selectable-mcps-to-staging

Conversation

@cole-ramer
Copy link
Collaborator

Selectable MCPs, Preset Grid, OAuth Refactor & Harness Duplication

Overview

This set of changes introduces a curated preset MCP server catalog, refactors OAuth connection management into a shared component, adds the ability to duplicate harnesses, and includes a comprehensive architecture document. The onboarding and harness-edit pages have been restructured to present MCPs through a selectable grid rather than requiring users to manually enter server URLs.


Changes by Area

1. Preset MCP Server Catalog (apps/web/src/lib/mcp.ts — new file)

A new shared data layer defines the types and registry for MCP servers:

  • McpServerEntry interface — the canonical shape for an MCP server (name, url, authType, optional authToken). Previously this interface was duplicated inline in both onboarding.tsx and $harnessId.tsx; it now lives in one place.

  • PresetMcpDefinition interface — extends a server entry with an id, human-readable description, iconName (either a Simple Icons slug or a full favicon URL), and category.

  • PRESET_MCPS — a static array of 9 pre-configured MCP servers users can select from:

    ID Name Auth Category
    junctionengine Junction Engine (Princeton courses) none student
    github GitHub oauth dev
    notion Notion oauth productivity
    linear Linear oauth productivity
    slack Slack oauth comms
    jira Jira oauth productivity
    awsknowledge AWS Knowledge none dev
    exa Exa (AI search) none web
    context7 Context7 (library docs) none dev
  • presetIdsToServerEntries(ids) — utility that maps an array of selected preset IDs back to their McpServerEntry objects, used when building the final MCP server list for harness creation.

2. Preset MCP Grid Component (apps/web/src/components/preset-mcp-grid.tsx — new file)

A reusable UI component that renders the preset catalog as a responsive checkbox grid (1–3 columns depending on screen width). Each card shows:

  • A logo fetched from cdn.simpleicons.org (with dark mode inversion) or a favicon URL, with a fallback to the first letter of the name if the image fails to load.
  • The server name and a short description.
  • A checkbox indicating selection state.

Used on both the onboarding page and the harness edit page.

3. Shared OAuth Connect Row (apps/web/src/components/mcp-oauth-connect-row.tsx — new file)

The OAuth connection UI for individual servers was previously duplicated between onboarding.tsx (97 lines) and $harnessId.tsx (80+ lines). This extracts it into a single shared component:

  • Shows the server name, URL, and a "Connect" button (or "Connected" badge if already authorized).
  • On click, starts the OAuth popup flow by calling GET /api/mcp/oauth/start, opens the authorization URL in a popup, and listens for the postMessage callback.
  • Properly cleans up event listeners and intervals on unmount via a ref-based cleanup pattern.

4. Onboarding Page Refactor (apps/web/src/routes/onboarding.tsx)

The onboarding wizard's MCP step has been restructured:

  • Before: Users could only manually add MCP servers by entering a name, URL, and auth type. OAuth connection state was tracked locally in component state (oauthConnected map) and wouldn't survive a page refresh.
  • After:
    • The MCP step now shows the PresetMcpGrid at the top, letting users select from popular servers with a single click.
    • Below the preset grid, a "or add custom" divider leads to the manual add form (preserved for advanced users).
    • State is split into customMcpServers (manually added) and selectedPresetMcps (preset IDs), merged at submission time via presetIdsToServerEntries.
    • The OAuth connect step now reads token status from Convex (api.mcpOAuthTokens.listStatuses) instead of local state, so connection status persists across sessions and is consistent with the rest of the app.
    • The inline OAuthConnectRow component (97 lines) has been removed in favor of the shared component.
    • The McpServerEntry interface is now imported from lib/mcp.ts instead of being defined locally.
    • Layout widened from max-w-2xl to max-w-3xl to accommodate the grid.

5. Harness Edit Page Refactor (apps/web/src/routes/harnesses/$harnessId.tsx)

The harness editor has been updated to match the new MCP selection pattern:

  • Preset grid integration: The MCP section now shows the PresetMcpGrid at the top. Servers that match a preset (by name + URL) are shown as checked in the grid; all others are treated as "custom" and displayed in the manual list below.
  • Derived state: selectedPresetMcps and customMcpServers are computed via useMemo from the current server list, avoiding duplicate state.
  • togglePresetMcp handler adds/removes preset servers from the list.
  • OAuth connections section: A new dedicated section appears below the MCP server list when OAuth servers are present. It uses the shared OAuthConnectRow and reads connection status from Convex via api.mcpOAuthTokens.listStatuses.
  • Removed OAuthStatusBadge: The old 80-line inline component that checked status via a REST call to /api/mcp/oauth/status has been replaced. OAuth servers now just show a static "OAuth" badge in the server row, with the actual connect/status UI in the dedicated section.
  • Layout widened from max-w-2xl to max-w-3xl.

6. Harness Duplication (packages/convex-backend/convex/harnesses.ts + apps/web/src/routes/harnesses/index.tsx)

Backend: A new duplicate mutation on the harnesses table:

  • Takes a harness ID, verifies ownership, and inserts a new harness with "Copy of <name>", preserving the model, status, MCP servers, and skills.
  • Sets a fresh lastUsedAt timestamp.

Frontend: The harness list page now supports duplication:

  • A new "Duplicate" option (with a Copy icon) appears in each harness card's dropdown menu.
  • The onDuplicate callback is threaded through HarnessGroupHarnessCard components.

7. Slack OAuth Support (packages/fastapi/app/config.py + packages/fastapi/app/services/mcp_oauth.py)

  • Config: Two new optional environment variables: SLACK_OAUTH_CLIENT_ID and SLACK_OAUTH_CLIENT_SECRET.
  • OAuth service: The _get_preregistered_client function now checks for Slack alongside GitHub. If the OAuth issuer URL contains slack.com and credentials are configured, it returns a pre-registered client — skipping Dynamic Client Registration (which Slack doesn't support from non-HTTPS origins).

8. Architecture Document (ARCHITECTURE.md — new file)

A 450-line architecture guide covering:

  • High-level system design (monorepo triangle: frontend ↔ Convex ↔ FastAPI)
  • Tooling & configuration files
  • Frontend routes, components, UI primitives, and library utilities
  • Convex schema (5 tables) and all query/mutation modules
  • FastAPI application core, routes, and services
  • Three detailed data flow walkthroughs (chat message, OAuth connection, tool execution)
  • A full Mermaid dependency diagram showing imports and network calls across all packages

Files Changed

File Status
ARCHITECTURE.md Added
apps/web/src/components/mcp-oauth-connect-row.tsx Added
apps/web/src/components/preset-mcp-grid.tsx Added
apps/web/src/lib/mcp.ts Added
apps/web/src/routes/harnesses/$harnessId.tsx Modified
apps/web/src/routes/harnesses/index.tsx Modified
apps/web/src/routes/onboarding.tsx Modified
packages/convex-backend/convex/harnesses.ts Modified
packages/fastapi/app/config.py Modified
packages/fastapi/app/services/mcp_oauth.py Modified

Selectable MCPs, Preset Grid, OAuth Refactor & Harness Duplication

Overview

This set of changes introduces a curated preset MCP server catalog, refactors OAuth connection management into a shared component, adds the ability to duplicate harnesses, and includes a comprehensive architecture document. The onboarding and harness-edit pages have been restructured to present MCPs through a selectable grid rather than requiring users to manually enter server URLs.


Changes by Area

1. Preset MCP Server Catalog (apps/web/src/lib/mcp.ts — new file)

A new shared data layer defines the types and registry for MCP servers:

  • McpServerEntry interface — the canonical shape for an MCP server (name, url, authType, optional authToken). Previously this interface was duplicated inline in both onboarding.tsx and $harnessId.tsx; it now lives in one place.

  • PresetMcpDefinition interface — extends a server entry with an id, human-readable description, iconName (either a Simple Icons slug or a full favicon URL), and category.

  • PRESET_MCPS — a static array of 9 pre-configured MCP servers users can select from:

    ID Name Auth Category
    junctionengine Junction Engine (Princeton courses) none student
    github GitHub oauth dev
    notion Notion oauth productivity
    linear Linear oauth productivity
    slack Slack oauth comms
    jira Jira oauth productivity
    awsknowledge AWS Knowledge none dev
    exa Exa (AI search) none web
    context7 Context7 (library docs) none dev
  • presetIdsToServerEntries(ids) — utility that maps an array of selected preset IDs back to their McpServerEntry objects, used when building the final MCP server list for harness creation.

2. Preset MCP Grid Component (apps/web/src/components/preset-mcp-grid.tsx — new file)

A reusable UI component that renders the preset catalog as a responsive checkbox grid (1–3 columns depending on screen width). Each card shows:

  • A logo fetched from cdn.simpleicons.org (with dark mode inversion) or a favicon URL, with a fallback to the first letter of the name if the image fails to load.
  • The server name and a short description.
  • A checkbox indicating selection state.

Used on both the onboarding page and the harness edit page.

3. Shared OAuth Connect Row (apps/web/src/components/mcp-oauth-connect-row.tsx — new file)

The OAuth connection UI for individual servers was previously duplicated between onboarding.tsx (97 lines) and $harnessId.tsx (80+ lines). This extracts it into a single shared component:

  • Shows the server name, URL, and a "Connect" button (or "Connected" badge if already authorized).
  • On click, starts the OAuth popup flow by calling GET /api/mcp/oauth/start, opens the authorization URL in a popup, and listens for the postMessage callback.
  • Properly cleans up event listeners and intervals on unmount via a ref-based cleanup pattern.

4. Onboarding Page Refactor (apps/web/src/routes/onboarding.tsx)

The onboarding wizard's MCP step has been restructured:

  • Before: Users could only manually add MCP servers by entering a name, URL, and auth type. OAuth connection state was tracked locally in component state (oauthConnected map) and wouldn't survive a page refresh.
  • After:
    • The MCP step now shows the PresetMcpGrid at the top, letting users select from popular servers with a single click.
    • Below the preset grid, a "or add custom" divider leads to the manual add form (preserved for advanced users).
    • State is split into customMcpServers (manually added) and selectedPresetMcps (preset IDs), merged at submission time via presetIdsToServerEntries.
    • The OAuth connect step now reads token status from Convex (api.mcpOAuthTokens.listStatuses) instead of local state, so connection status persists across sessions and is consistent with the rest of the app.
    • The inline OAuthConnectRow component (97 lines) has been removed in favor of the shared component.
    • The McpServerEntry interface is now imported from lib/mcp.ts instead of being defined locally.
    • Layout widened from max-w-2xl to max-w-3xl to accommodate the grid.

5. Harness Edit Page Refactor (apps/web/src/routes/harnesses/$harnessId.tsx)

The harness editor has been updated to match the new MCP selection pattern:

  • Preset grid integration: The MCP section now shows the PresetMcpGrid at the top. Servers that match a preset (by name + URL) are shown as checked in the grid; all others are treated as "custom" and displayed in the manual list below.
  • Derived state: selectedPresetMcps and customMcpServers are computed via useMemo from the current server list, avoiding duplicate state.
  • togglePresetMcp handler adds/removes preset servers from the list.
  • OAuth connections section: A new dedicated section appears below the MCP server list when OAuth servers are present. It uses the shared OAuthConnectRow and reads connection status from Convex via api.mcpOAuthTokens.listStatuses.
  • Removed OAuthStatusBadge: The old 80-line inline component that checked status via a REST call to /api/mcp/oauth/status has been replaced. OAuth servers now just show a static "OAuth" badge in the server row, with the actual connect/status UI in the dedicated section.
  • Layout widened from max-w-2xl to max-w-3xl.

6. Harness Duplication (packages/convex-backend/convex/harnesses.ts + apps/web/src/routes/harnesses/index.tsx)

Backend: A new duplicate mutation on the harnesses table:

  • Takes a harness ID, verifies ownership, and inserts a new harness with "Copy of <name>", preserving the model, status, MCP servers, and skills.
  • Sets a fresh lastUsedAt timestamp.

Frontend: The harness list page now supports duplication:

  • A new "Duplicate" option (with a Copy icon) appears in each harness card's dropdown menu.
  • The onDuplicate callback is threaded through HarnessGroupHarnessCard components.

7. Slack OAuth Support (packages/fastapi/app/config.py + packages/fastapi/app/services/mcp_oauth.py)

  • Config: Two new optional environment variables: SLACK_OAUTH_CLIENT_ID and SLACK_OAUTH_CLIENT_SECRET.
  • OAuth service: The _get_preregistered_client function now checks for Slack alongside GitHub. If the OAuth issuer URL contains slack.com and credentials are configured, it returns a pre-registered client — skipping Dynamic Client Registration (which Slack doesn't support from non-HTTPS origins).

8. Architecture Document (ARCHITECTURE.md — new file)

A 450-line architecture guide covering:

  • High-level system design (monorepo triangle: frontend ↔ Convex ↔ FastAPI)
  • Tooling & configuration files
  • Frontend routes, components, UI primitives, and library utilities
  • Convex schema (5 tables) and all query/mutation modules
  • FastAPI application core, routes, and services
  • Three detailed data flow walkthroughs (chat message, OAuth connection, tool execution)
  • A full Mermaid dependency diagram showing imports and network calls across all packages

Files Changed

File Status
ARCHITECTURE.md Added
apps/web/src/components/mcp-oauth-connect-row.tsx Added
apps/web/src/components/preset-mcp-grid.tsx Added
apps/web/src/lib/mcp.ts Added
apps/web/src/routes/harnesses/$harnessId.tsx Modified
apps/web/src/routes/harnesses/index.tsx Modified
apps/web/src/routes/onboarding.tsx Modified
packages/convex-backend/convex/harnesses.ts Modified
packages/fastapi/app/config.py Modified
packages/fastapi/app/services/mcp_oauth.py Modified

@DIodide DIodide self-requested a review March 18, 2026 00:26
@claude
Copy link

claude bot commented Mar 18, 2026

Claude finished @DIodide's task in 1m 45s —— View job


Code Review

  • Read changed files
  • Analyze code for issues

Overall this is a solid, well-structured PR. The refactoring decisions (shared OAuthConnectRow, centralized mcp.ts, derived preset state) are good. A few bugs and one security issue worth addressing before merge.


Bug: User-visible debug text in Slack description

apps/web/src/lib/mcp.ts:68

description: "(wait until deployed)Send messages, read channel history, and search conversations.",

This note is rendered directly in the preset grid that end users see. Strip the (wait until deployed) prefix before merging.

Fix this →


Bug: Inconsistent isSelected check in togglePresetMcp

apps/web/src/routes/harnesses/$harnessId.tsx:146-155

const isSelected = currentMcpServers.some(
    (s) => s.name === preset.server.name,  // name-only check
);
setMcpServers(
    isSelected
        ? currentMcpServers.filter(
                (s) => !(s.name === preset.server.name && s.url === preset.server.url),  // name+URL check
            )
        : [...currentMcpServers, preset.server],
);

If a user has a custom server with the same name as a preset but a different URL, isSelected is true (name match), but the filter won't remove it (URL mismatch), so the preset server gets added alongside the existing one instead of toggled off. The isSelected check should also use && s.url === preset.server.url to be consistent with the filter.

Fix this →


Security: Missing event.origin validation in OAuth message listener

apps/web/src/components/mcp-oauth-connect-row.tsx:51-62

const handler = (event: MessageEvent) => {
    if (event.data?.type === "mcp-oauth-callback") {
        cleanup();
        if (event.data.success) {
            toast.success(`Connected to ${server.name}`);
        }
        ...
    }
};
window.addEventListener("message", handler);

Any page loaded in any iframe or tab can postMessage with { type: "mcp-oauth-callback", success: true } to fake a successful OAuth connection. The handler must check event.origin against the expected callback origin (your app's own origin for the redirect page) before trusting the message.

Fix this →


Type inconsistency: HarnessCard missing "oauth" from authType

apps/web/src/routes/harnesses/index.tsx:281-284

mcpServers: Array<{
    name: string;
    url: string;
    authType: "none" | "bearer";  // missing "oauth"!
    authToken?: string;
}>;

HarnessGroup (line 228) correctly includes "oauth" but HarnessCard drops it. TypeScript may silently accept this due to structural subtyping, but it could cause runtime issues if the card ever needs to inspect authType === "oauth". Should match the HarnessGroup definition.

Fix this →


UX: Duplicate action has no feedback

apps/web/src/routes/harnesses/index.tsx:81-83

const handleDuplicate = (id: Id<"harnesses">) => {
    duplicateHarness.mutate({ id });
};

No loading state, no success toast, no error handling. Unlike handleToggleStatus which at least triggers a re-render, duplicating is silent — users won't know if it succeeded or failed. Adding a toast.success("Harness duplicated") in an onSuccess callback would be a quick win.


Minor: In-memory OAuth state won't survive multi-instance deployments

packages/fastapi/app/services/mcp_oauth.py:36-37

_pending_oauth: dict[str, "PendingOAuth"] = {}

The PKCE state → verifier map is process-local. If FastAPI is ever run with multiple workers or replicated pods, the OAuth callback may land on a different instance and fail with "Invalid or expired OAuth state". Probably fine for now, but worth a comment noting this limitation for when you scale out.


Summary

Severity Issue File
🔴 Security Missing event.origin check in OAuth message handler mcp-oauth-connect-row.tsx
🟠 Bug isSelected uses name-only check, filter uses name+URL $harnessId.tsx
🟠 Bug Slack description has (wait until deployed) prefix mcp.ts
🟡 Type HarnessCard.authType missing "oauth" index.tsx
🟡 UX Duplicate action gives no user feedback index.tsx
ℹ️ Note In-memory OAuth state won't survive multi-instance mcp_oauth.py

@DIodide
Copy link
Owner

DIodide commented Mar 18, 2026

@cole-ramer fix type inconsistency (one line add) and Bug: Inconsistent isSelected check in togglePresetMcp, also add a toast for harness duplication

@DIodide
Copy link
Owner

DIodide commented Mar 18, 2026

@claude review the last commit

@claude
Copy link

claude bot commented Mar 18, 2026

Claude finished @DIodide's task in 1m 12s —— View job


Review of Last Commit (e54bea5 — "bug fixes")

  • Gather context
  • Read changed files
  • Analyze code
  • Post review

This commit addresses 3 of the 6 issues from the previous review. Here's the breakdown:

What Was Fixed

$harnessId.tsxisSelected name+URL check

// Before
const isSelected = currentMcpServers.some(
    (s) => s.name === preset.server.name,
);
// After
const isSelected = currentMcpServers.some(
    (s) => s.name === preset.server.name && s.url === preset.server.url,
);

Correct — now consistent with the filter condition below it.

index.tsx — Duplicate toast

duplicateHarness.mutate(
    { id },
    { onSuccess: () => toast.success("Harness duplicated") },
);

Good. One small note: there's no onError handler, so silent failures are still possible. Consider adding { onError: () => toast.error("Failed to duplicate harness") } alongside onSuccess.

index.tsxHarnessCard.authType includes "oauth"

authType: "none" | "bearer" | "oauth";

Fixed correctly.


Still Open

Severity Issue File
🔴 Security Missing event.origin check in OAuth message handler mcp-oauth-connect-row.tsx:52
🟠 Bug Slack description still has (wait until deployed) prefix mcp.ts:68

The security issue is the most important remaining one — any page in any tab can spoof a successful OAuth connection by posting { type: "mcp-oauth-callback", success: true }. Fix:

const handler = (event: MessageEvent) => {
+   if (event.origin !== window.location.origin) return;
    if (event.data?.type === "mcp-oauth-callback") {

Fix security issue →

Fix Slack description →

@DIodide DIodide merged commit 9347bc6 into staging Mar 18, 2026
1 check passed
@DIodide DIodide deleted the feat/selectable-mcps-to-staging branch March 18, 2026 18:13
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.

2 participants