<!-- VORFLUX_AGENT_PR_BODY_BEGIN -->
Adds the full 654-entry MCP directory without bundling records into client JavaScript, with explicit capability status and connector branding that degrades safely when no authoritative logo is available.
## Changes
- Lazy-load and validate the searchable, filterable, progressively rendered MCP catalog.
- Render same-origin proxied provider icons for 543 entries, with a reviewed domain allowlist and deterministic fallback marks for 111 unresolved or unbranded entries.
- Record OAuth discovery capability separately from end-to-end support; all directory setup actions remain suppressed until their authentication flow is verified.
- Add a reproducible OAuth metadata probe with HTTPS/private-network protections, stable URL keys, authorization-server scanning, and catalog fingerprint validation.
- Add Google Drive branding for the curated built-in connector.
## Testing
- **Passed:** Deterministic generation and catalog assertions.
```bash
PATH="$HOME/.bun/bin:$PATH" python3 apps/web/scripts/generate-mcp-directory.py --output
cmp apps/web/public/mcp-directory.json
```
Verified 654 entries, 254 DCR discoveries, 27 preregistered OAuth discoveries, 373 unclassified entries, and zero directory setup actions.
- **Passed:** Stale OAuth metadata fingerprint is rejected by the generator.
- **Passed:** Touched-file Biome checks and `git diff --check`.
- **Passed:** Icon proxy returned 200 for an allowlisted domain and 400 for an unknown valid-looking domain.
- **Passed:** Authenticated desktop/mobile browser inspection and conservative capability labels.
- **Passed:** Public preview returned HTTP 200 and rendered the real app. Authentication cookies do not transfer to the public hostname, so the public screenshot shows login.
- **Partial:** Repository-wide TypeScript checks remain blocked by unrelated existing errors outside the touched MCP files.
- **Partial:** 111 entries intentionally retain deterministic fallback marks; endpoint-derived domains may not always be the canonical brand logo.
- **Blocked:** Google rejected the local HTTP OAuth callback, so live Google Drive consent, callback, persistence, tool discovery, disconnect, and reconnect were not completed.
Public preview: https://ar8ruchhbi65.preview.us1.vorflux.com/configure/tools
---
**Attached Images**
*[288.csv]*
*[mcp-directory-final.json]*



<!-- VORFLUX_AGENT_PR_BODY_END -->
---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/1cd0aab9-2a45-4818-aa13-f9bfe032ddba)
- Requested by: Dhravya Shah (dhravya@supermemory.com)
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes how users pick MCP URLs and auth (OAuth vs API key) before hitting existing connect endpoints; no new backend auth logic in this diff, but misconfiguration or trusting bad URLs remains a user-risk surface.
>
> **Overview**
> Adds a **browseable MCP directory** on the Company Brain connectors page: the catalog is **not bundled in JS**—it loads from static **`/mcp-directory.json`** only after the user opens the directory (with validation, caching, and abort handling).
>
> The new **`McpDirectoryBrowser`** supports search, category/availability filters, and progressive “show more” rendering. Supported remote entries route into the existing custom MCP flow via **Set up**, which pre-fills name/URL and opens the connector dialog with context-specific copy.
>
> The custom connector dialog now uses an explicit **OAuth vs API key** toggle; API key fields only appear for API-key mode, and directory-backed connections get **stable slugs** (`-dir-` suffix) so names display cleanly on connected cards. **Middleware** excludes `mcp-directory.json` from the auth matcher so the asset can be fetched publicly.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 8b59bae84a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Stack Context
Single-auth story for the Claude Code supermemory plugin rework: the plugin's hooks and its MCP surface share one credential (`sm_` API key from the existing browser connect flow). That requires `mcp.supermemory.ai` to accept plain API keys, which it currently rejects (OAuth JWT only).
## What?
- `validateApiKey()` in `server/auth`: `sm_`-prefixed Bearer tokens validate via the existing `fetchSession()` (`GET /v3/session`) and map to the same `AuthUser` shape as OAuth tokens (`userId` ← `user.id`, `organizationId` ← `org.id`, the key itself as `bearerToken` for downstream API calls). Successful lookups cached per isolate for 60s.
- `handleMcpRequest` routes by token shape: `sm_` keys → session validation, everything else → OAuth JWT verification (unchanged).
- `sessionInfoSchema` now types the `org.id` field the session endpoint already returns.
## Why?
MCP clients that already hold an API key (Claude Code plugin hooks, CLI, scripts) can connect without an OAuth dance or a second consent. OAuth behavior is untouched — the existing "rejects opaque API keys" test on the OAuth validator still passes; keys just get their own path. Malformed keys are rejected without an API round-trip.
Tests: 4 new cases (valid key → AuthUser, cache hit → single fetch, 401 → null, malformed → no request). `vitest run src/server/auth` 13/13, `tsc --noEmit` clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds a new authentication path on the MCP entrypoint with in-memory key caching (60s TTL), so revoked keys may remain valid briefly within an isolate; OAuth behavior is unchanged.
>
> **Overview**
> MCP Bearer auth now accepts **`sm_` Supermemory API keys** in addition to OAuth JWTs, so clients that already hold an API key can connect without OAuth.
>
> **`validateApiKey`** treats keys matching `sm_` plus at least 17 non-space characters as API keys: it calls **`GET /v3/session`** with the key as Bearer, maps **`user.id`** and **`org.id`** into the same **`AuthUser`** shape as OAuth (key kept as **`bearerToken`** for downstream API calls), and caches successful results per isolate for **60s** (up to 1000 entries, full clear on overflow). Malformed keys are rejected locally with no HTTP call; session **401** yields unauthenticated.
>
> **`handleMcpRequest`** branches on token shape: API keys go through session validation; other tokens still use JWT verification unchanged.
>
> **`sessionInfoSchema`** now includes optional **`org.id`** typing for session responses used when resolving organization context from API keys.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e54fb11bf1598d07a807eb2b0b63a347aaa58fb6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Returning from Stripe remounts onboarding and reseeds the domain from the user's email, so the header showed the wrong company and a research retry would re-run on the wrong domain. Past the confirm step, read the org's stored brainWorkspaceDomain instead.
Two hand-rolled copies of the add-on/brainMode rule replaced by a single shared helper, and the one-line isCompanyBrainOrganization wrapper dropped. No behaviour change.
## Summary
- Reorder Apps & extensions so Import X bookmarks appears in the top row and Apple Shortcuts uses the open space below.
- Keep both Apple Shortcut actions inline on larger screens while allowing the card to grow only as much as needed.
- Rework the Company Brain promo on phones so its logo, copy, close control, and CTA remain readable and aligned.
## What?
Adds a "Continue with AgentID" button to the web app's login page, matching the existing Google/GitHub buttons (same `ExternalAuthButton` pattern, PostHog `login_attempt` capture, last-used badge).
- `packages/lib/auth.ts`: adds the `genericOAuthClient` plugin — generic OAuth providers sign in via `signIn.oauth2({ providerId })`, not `signIn.social`.
- `apps/web/app/(auth)/login/page.tsx`: the button, gated the same way as the other social buttons — always shown on cloud (`NEXT_PUBLIC_HOST_ID === "supermemory"`), opt-in elsewhere via `NEXT_PUBLIC_AGENTID_AUTH_ENABLED` (added to `.env.example`).
## Why?
Companion to supermemoryai/mono#2908, which registers an `agentid` generic OAuth provider (OIDC against auth.agentid.com) on the API so agents can authenticate with their AgentID identity. The consumer app talks to the same better-auth server, so it gets the same sign-in option. mono#2916 additionally auto-invites the agent's verified human owner to the agent's workspace.
Requires mono#2908 to be deployed for the button to work; until then the API rejects the unknown provider and the page shows its normal error state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches authentication entry points and OAuth client configuration; risk is moderate because it extends login surface area but follows existing social sign-in patterns and is feature-flagged.
>
> **Overview**
> Adds **Continue with AgentID** on the web login page, using the same `ExternalAuthButton` flow as Google/GitHub (PostHog `login_attempt`, last-used badge, loading/error handling).
>
> The button calls **`signIn.oauth2({ providerId: "agentid" })`** instead of `signIn.social`, enabled by registering **`genericOAuthClient`** on the shared better-auth client in `packages/lib/auth.ts`.
>
> Visibility matches other social providers: shown on cloud when `NEXT_PUBLIC_HOST_ID === "supermemory"`, or elsewhere when **`NEXT_PUBLIC_AGENTID_AUTH_ENABLED`** is set (documented in `.env.example`). Depends on the API registering the `agentid` generic OAuth provider.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 90a32786a3. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Why?
Plain **T-1554**: a user with a **read-only** MCP OAuth grant got 403s on memory listing, and the client rendered them as *"Access forbidden. Your account may be restricted or blocked."* The API's actual error body said `{"error": "This API key has read-only access"}` — but `handleError` discarded it, so the user (and support) chased a nonexistent account ban.
Two masking layers:
1. `handleError` used the raw error `message`, which for our raw-fetch endpoints was a hardcoded string ("Failed to fetch documents") or unparsed JSON, and fell back to the scary "restricted or blocked" text when empty.
2. `getDocuments` didn't read the response body at all.
## What?
- New `extractApiErrorMessage()` unwraps JSON error bodies (`{"error": ...}` / `{"message": ...}`) so the API's real reason reaches the user.
- `getDocuments` and `listMemoryEntries` now pass the (unwrapped) response body through with the status, letting `handleError` apply status-aware fallbacks when the body is empty.
- Reworded the empty-body 403 fallback to point at the common cause first: *"Access forbidden. This connection may be read-only or scoped to specific spaces — reconnect with broader access, or check your account status."*
Companion API-side fix (read-only grants couldn't call semantically-read POST list endpoints at all): supermemoryai/mono#2772.
## Testing
- Added tests: a 403 with a JSON error body surfaces the API's message; an empty-body 403 gets the scope-aware fallback. `vitest run src/server/client/index.test.ts` — 3 passed.
- `tsc --noEmit -p tsconfig.json` clean. (The `check-types` script also runs `tsconfig.widget.json`, which fails on origin/main with a pre-existing `UseAppOptions.strict` error, unrelated.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> User-facing error text only in the MCP client; no auth or API behavior changes.
>
> **Overview**
> **MCP client errors now show what the API actually returned** instead of hardcoded strings or misleading “restricted or blocked” text.
>
> Adds `extractApiErrorMessage()` to parse JSON bodies (`error` / `message` fields) from failed responses. **`getDocuments`** and **`listMemoryEntries`** read the response body on non-OK status and attach the unwrapped message (with status) for **`handleError`**, which also uses the helper on error messages. When a 403 has no body message, the fallback now points users toward **read-only or scoped OAuth** rather than an account ban.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1f492470cf. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Adds a focused guide for backfilling dated documents with `documentDate` and the batch ingestion API.
- includes TypeScript and Python batch examples plus optional completion polling
- links the guide from the docs navigation and ingestion entry points
Validated with `bunx mintlify@latest validate` and `bunx mintlify@latest broken-links`.
Onboarding now opens a trial step that collects a card through Stripe checkout before the brain is enabled, with a timeline showing today's $0, the day-12 reminder, and the day-14 charge.
- Only leaves the card step once the API confirms the trial is live
- Brain home shows a setup banner and dims what the trial unlocks
- Recovers orgs that abandoned checkout instead of stranding them
- Adds the organization ID to account settings, copyable from the label
Company Brain workspaces could only buy Scale at $399/mo, which is roughly eight times what the median team uses. Adds the $100/mo Max card to the Company Brain plan picker, notes what a Scale trial loses on the way down, and flags that Scale is cheaper above about $400/mo of credits.
Inline notice with app icons when a channel automation can't use personal-only connections (footer, next to Save), post-save warning toast from the API, templates capped to 3 connection-relevant ideas with a show-all toggle, and New automation promoted to a primary button on the heading row. Pairs with mono #2724; degrades gracefully without it.
Fixes ENG-1151
Adds one FAQ to the ChatGPT Web setup guide for making Supermemory the default memory.
- shows how to disable ChatGPT memory and optionally move the existing summary
- adds light and dark screenshots plus the custom instruction to paste
Tested with `mintlify validate` and `mintlify broken-links`.
### TL;DR
Documents the new `ids` parameter for the `forget-matching` endpoint, allowing exact memory deletion without semantic search.
### What changed?
The `forget-matching` endpoint now accepts either a `query` (semantic search-based forgetting) or an explicit `ids` list (direct deletion by memory ID) — one or the other must be provided. The docs have been updated to reflect this:
- The `query` parameter is now marked as `one of*` rather than required, and `ids` is introduced as an alternative with the same mutual-exclusivity constraint.
- `threshold` is clarified as applying to `query` mode only.
- Code examples for both JavaScript and cURL now show the recommended two-step pattern: run a `dryRun` with `query`, then apply using the `ids` returned from the preview — avoiding drift if the container changes between steps.
- A new `<Tip>` block explains why passing `ids` on the apply step produces a more deterministic delete than re-running the `query`.
### How to test?
1. Call `forget-matching` with `dryRun: true` and a `query` to retrieve candidate `id`s.
2. Re-call with `dryRun: false` and the `ids` from step 1 to confirm only those exact memories are forgotten.
3. Verify that passing `ids` belonging to a different `containerTag` are ignored.
4. Confirm that providing both `query` and `ids`, or neither, returns an appropriate validation error.
### Why make this change?
Re-running a `query` on the apply step can produce different results if the container was modified between the preview and the apply. Exposing `ids` as a first-class parameter lets callers pin the delete to exactly the set they reviewed, making bulk forgetting safer and more predictable.
Header-auth MCP servers like Plane need an API key plus a second header,
which the custom connector dialog had no way to collect.
- Restructured on the Claude connector pattern: name + URL up front,
credentials behind collapsed Advanced settings
- Adds API key, header name, and repeatable extra header rows
## What and why
Make skills created through Company Brain visible in the existing Settings catalog without a manual reload. The UI labels Slack-originated skills and refreshes the catalog while the Settings page is open.
```mermaid
flowchart LR
A[Slack approval] --> B[Company Brain skill catalog]
B --> C[Skills API query]
C --> D[Settings Skills list]
D --> E[Created by Company Brain label]
```
## Validation
- `bunx biome check apps/web/components/settings/company-brain-skills.tsx apps/web/components/settings/company-brain-skills/domain.ts apps/web/components/settings/company-brain-skills/skill-row.tsx apps/web/hooks/use-brain-skills.impl.ts` — passed
## Impact
Settings polling refreshes every 15 seconds and on window focus. It does not change the Nova/browser chat workflow or create skills from that surface.
Updates the Supermemory MCP docs for the revamped tool, space, widget, and OAuth flows.
- adds a screenshot-backed ChatGPT Web setup guide with light and dark variants
- refreshes the overview, setup, tools, spaces, and widget docs
- keeps manual JSON configuration in a dropdown
Tested all four local MCP docs routes and image references.
for mcp image light mode -> dark mode images and dark mode has light mode (since the background was blending in i made this way to refocus user attention on the screenshots)
Fixes cross-host graph rendering and moves widget uploads off the JSON/base64 tool transport.
- render graph data from the launcher result without a second tool call
- stream multipart uploads through one-time, short-lived upload sessions
- remove temporary widget diagnostics and redundant unit tests
Tested with Biome, TypeScript, 17 unit tests, a Wrangler deployment dry-run, and live graph rendering in ChatGPT and Claude. Authenticated E2E setup is currently blocked by the saved OAuth refresh session returning `invalid_grant: session not found`.
Keep docs asset URLs repo-root-relative and pass images as literal MDX children so Mintlify can compile them through OptimizedImage under the /docs mount.
Mintlify's official guidance says: “Image paths are root-relative from your docs repository.” It also says relative paths such as `./screenshot.png` are unsupported. See [Image embeds](https://www.mintlify.com/docs/create/image-embeds).
The existing `/images/...` paths were correct. The failure came from passing them through custom-component string props, which kept Mintlify from seeing those images during its MDX transform.
- fix hero, building-block, and Slack avatar images
- preserve Slack avatar clipping
- normalize Hermes and Company Brain icon sizing
Tested with Mintlify validation, broken-link checks, and browser checks across every changed route.
Fixes broken docs navigation and asset paths from the site audit.
- serves docs images from `/images` and adds the missing Cartesia icon
- corrects homepage, console, and LinkedIn destinations
- removes agent-only comparison headings from the web TOC
Tested with Mintlify validate and Mintlify broken-links.
- Header pill with trial days left (Autumn-first, org metadata fallback)
- Brain home: Your Company Brain timeline card (trial strip, milestones from /brain/overview) promoted to top-right
- Trial copy in CB onboarding Slack step and docked header
- Brain home now reads the new /brain/overview endpoint (drops the dead /brain/connections fetch)
Fixes ENG-1142
## Stack Context
This is the frontend half of the Company Brain Skills feature. The harness is implemented in supermemoryai/mono#2611.
## What?
Add Skills settings with separate Org-wide and Personal sections, Markdown upload autofill, scoped creation and editing, approval controls, and server-driven permissions.
## Why?
Members need a focused way to manage their private playbooks while admins create and approve organization-wide guidance.
## Related
- Harness: https://github.com/supermemoryai/mono/pull/2611
---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/d8c77451-8f55-47ab-a182-5c98da616263)
- Requested by: Sreeram Sreedhar (sreeram@supermemory.com)
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
Fixes MCP app submission metadata and makes widget delivery consistent across hosts.
- content-hash widget resources and set the production widget domain
- add typed structured outputs to direct tools and scope default-space calls
- keep graph rendering compatible with both initial results and app-side loading
Tested with `bun run check-types` and `bun run test:unit` (42 tests).
Adjusts login handling for a limited credential-validation flow. Normal Google and magic-link sign-ins are unchanged for other accounts.
Tested with direct login validation and a production build.
ChatGPT can omit nullable nested fields from structured tool results and does not reliably expose result metadata to MCP Apps. Keep graph data in `structuredContent`, tolerate missing document titles, and bundle React inside the widget so the graph renders consistently across hosts.
- use the deployable `app-v4` resource URI
- keep the graph response and widget schema aligned
- remove the `esm.sh` runtime dependency
Tested with typecheck, 42 unit tests, production widget build, 36 branch-local authenticated E2E tests, and a live ChatGPT graph render.
Moves MCP App results onto explicit runtime schemas so hosts can distinguish model-visible output from widget-only graph data.
- advertises output schemas for structured tools
- validates API, session, and widget boundaries instead of asserting response types
- marks additive app writes as non-destructive and removes redundant widget typing
Verified with MCP typecheck, 42 unit tests, and the production widget build. Claude Desktop host validation is in progress against a temporary tunnel.
Agent switcher snapped back to the just-connected plugin while the API-key banner was open; fixed the effect so manual tab clicks stick. Replaced the double header + blurred install-steps overlay with named chip tabs (matching existing chip style), inline Connect + Docs, and a plain step-title preview.
Fixes ENG-1141