The MCP transport rejects JSON-RPC bodies over 4 MiB with a bare 413, so
large file uploads died with a generic "Upload failed" widget error. The
upload widget now rejects oversized files at selection time with the size
limit spelled out, advertises the limit in the dropzone copy, and maps any
transport 413 that still occurs to the same friendly message.
Co-authored-by: Cursor <cursoragent@cursor.com>
Return structured error views from set-active-tag so the widget shows clear user-facing copy instead of a generic unrecognized-response error. Validate the resolved workspace in search_memory, and distinguish tag list refresh failures from genuinely missing tags.
Co-authored-by: Cursor <cursoragent@cursor.com>
set-active-tag accepted any tag string even if it did not exist, and
search_memory surfaced a generic "Access forbidden" 403 for unknown tags.
Both tools now validate the tag against the user's container tag list and
return a clear "does not exist" error pointing at listSpaces.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix CSS specificity order: move .mcp-widget-glow base rule before
override selector to satisfy noDescendingSpecificity
- Remove invalid aria-label from header element
- Add aria-hidden="true" to decorative SVG instead of role="img"
- Format multi-line shadow values on single lines per Biome
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Make the widget fall back to dark tokens when the host does not provide an explicit theme, and expand workspace memory count labels from mem/mems to memory/memories.
Co-authored-by: Ishaan Gupta <ishaankone@gmail.com>
Resolve MCP revamp conflicts by keeping the new server layout, deleting legacy root MCP files, and carrying forward API timeout handling into the new auth/client modules.
Consumers think in "spaces", not "container tags". Renames the tool to
listSpaces and updates its description + text output to say "spaces".
Internal plumbing is unchanged: the client method listContainerTags() and
the structuredContent.containerTags key (widget contract) keep their names.
Functionality unchanged; just the name. The DO class is now SupermemoryMCP, which matches the existing supermemory-mcp worker's v1 migration -> the cutover is a no-op, no DO session reset. Demo/mock 'enterprise buyers' text left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the old apps/mcp with the enterprise-mcp code (functionality unchanged). package.json: worker supermemory-mcp, catalog deps pinned to explicit versions, portless dev:app. wrangler: name supermemory-mcp, route mcp.supermemory.ai, DO EnterpriseMCP. Builds + boots + OAuth discovery/401/worker-to-api verified locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Makes the MCP `recall` tool render memories the same way the console **playground** does — rich, scored, relation-aware output — instead of the old flat `### Memory N (NN% match)` list.
The playground pattern is: **profile + rich search → one shared `formatMemories` formatter → feed to the model.** This ports that into the standalone MCP server.
## Changes
- **`format.ts`** (new) — `formatMemories`, lifted verbatim from the playground. Pure, dependency-free. Renders similarity scores, `agg`/`chunk` markers, related-memory arrows (`←` parent, `→` child, `~` related), attached document summaries, and temporal context.
- **`client.ts`** — `SupermemoryClient.search()` now accepts the playground's retrieval knobs (`searchMode` / `rerank` / `rewriteQuery` / `include`) and **stops discarding** the rich response fields (`context`, `documents`, `metadata`, `isAggregated`) during normalization, so the formatter has data to render.
- **`server.ts` (`handleRecall`)** — retrieves memories via `client.search()` with `include: { documents, relatedMemories }`, prepends the user's profile (stable + recent facts) when `includeProfile` is set, and renders memories through `formatMemories`. Output capped at `MAX_RECALL_CHARS`.
## How it maps to the playground
| | Playground | MCP (this PR) |
|---|---|---|
| Profile | injected into its own system prompt (`<user_profile>`) | prepended to `recall` output / exposed via `context` prompt |
| Search | hook pre-fetch **or** tool | `recall` tool (model-triggered) |
| Formatting | `formatMemories` → system prompt | `formatMemories` → `recall` tool result |
## Testing
Verified end-to-end against a local API + local MCP, driven through a real MCP client (and Claude Desktop). `recall` now returns scored memory blocks with `Source:`/`Document:` lines and the legend header. Existing e2e assertions remain compatible (they match `## User Profile` / `## Relevant Memories`, both still emitted).
## Notes
- One deliberate gap vs the playground: `aggregate` — the public SDK's `search.memories` doesn't expose it, so no `agg` synthesis blocks. Relation arrows render only when memories actually have linked parent/child versions.
- Possible follow-up: expose the `include` / `rerank` knobs as `recall` tool params so callers can toggle relations/documents per call.
- Pre-existing `tsc` errors in `server.ts` (dual `@modelcontextprotocol/sdk` install) and `mcp-app.ts` (DOM `lib`) are unrelated to this change — confirmed by an unchanged baseline.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/c616fc88-a725-4961-9011-712bce740601)
- Requested by: Sreeram Sreedhar (sreeram@supermemory.com)
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
End-to-end tests that drive the deployed Supermemory MCP over streamable HTTP (no mocks):
- discovery: handshake + tool/resource/prompt listing
- identity: whoAmI, listProjects
- memory: save -> recall round-trip, profile variants, forget, container scoping
- graph/resources/prompts: memory-graph, fetch-graph-data, profile/projects, context
- oauth: protected-resource discovery chain, dynamic client registration,
token-endpoint negatives, and a real refresh -> access token round-trip
- auth: GET / info, 401 on missing/invalid token
- root-scope: x-sm-project scoping behavior
Tests skip without SUPERMEMORY_API_KEY (OAuth tier-D skips without its token env vars) so CI is safe without secrets. Adds vitest + a test:e2e script and documents running + the OAuth flow in apps/mcp/README.md.
## Summary
Fixes MCP OAuth discovery so the client-server handshake actually validates against the **MCP 2025-06-18 authorization spec** (which adopts RFC 9728 Protected Resource Metadata + RFC 8707 Resource Indicators).
Previously, a client connecting to `https://mcp.dev.supermemory.ai/mcp` would receive `resource: "https://mcp.supermemory.ai"` (bare host, prod fallback) and reject the connection:
> Protected resource https://mcp.supermemory.ai does not match expected https://mcp.dev.supermemory.ai/mcp (or origin)
## Changes
- **`resource` now includes the `/mcp` endpoint path** — the spec wants the canonical MCP server URI, and the bundled `@modelcontextprotocol/sdk` reference implementation emits the same shape (`new URL(rsPath, base).href`). Bare-host worked with lenient clients that fell back to origin-matching; strict clients rejected it.
- **Path-suffixed metadata route** added at `/.well-known/oauth-protected-resource/mcp` alongside the bare path. The SDK's `metadataHandler` mounts under the resource path, so this matches what spec-strict clients probe first.
- **`WWW-Authenticate`'s `resource_metadata` URL** points to the canonical full URL (`https://host/.well-known/oauth-protected-resource/mcp`).
- **Centralized base-URL derivation** in a new `mcpBaseUrl()` helper, with priority:
1. `MCP_URL` env var — set by portless dev script so dev requests resolve to the tunneled host, not whatever the local proxy sticks in `Host`
2. `x-forwarded-host` / `host` request headers
3. `https://mcp.supermemory.ai` last-resort fallback (only hit when the worker can't see the inbound host at all)
## Production impact
`MCP_URL` is dev-only (not in `wrangler.jsonc` vars), so prod falls through to the `Host` header → `https://mcp.supermemory.ai/mcp`. The wire change in prod is that `resource` now ends with `/mcp` instead of being bare — spec-correct, what strict clients require, and tolerated by lenient ones.
## Contributor DX
Added `apps/mcp/.dev.vars.example` documenting `API_URL`, `MCP_URL`, and `POSTHOG_API_KEY` for contributors running plain `wrangler dev` without portless.
## Test plan
- [x] `curl https://mcp.dev.supermemory.ai/.well-known/oauth-protected-resource` returns `resource: https://mcp.dev.supermemory.ai/mcp`
- [x] `curl https://mcp.dev.supermemory.ai/.well-known/oauth-protected-resource/mcp` returns the same payload
- [x] 401 from `/mcp` carries `WWW-Authenticate: Bearer resource_metadata="…/oauth-protected-resource/mcp"`
- [x] MCP client (vscode extension) connects successfully — previously failed with the resource-mismatch error
- [ ] Verify in prod that bare-host clients continue to work after deploy