supermemory/apps/mcp
Dhravya 5ecbc26345
fix(mcp): surface real API error messages instead of 'restricted or blocked' (#1406)
## 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 -->
2026-08-14 22:36:52 +00:00
..
e2e fix mcp graph and file uploads (#1397) 2026-08-04 18:58:14 +00:00
scripts fix mcp app contracts (#1394) 2026-08-01 06:41:17 +00:00
src fix(mcp): surface real API error messages instead of 'restricted or blocked' (#1406) 2026-08-14 22:36:52 +00:00
.dev.vars.example fix(mcp): align oauth protected-resource metadata with MCP 2025-06-18 spec (#945) 2026-05-15 23:56:25 +00:00
.gitignore chore: update readme and gitignore (#640) 2025-12-31 02:41:35 +00:00
package.json fix mcp app contracts (#1394) 2026-08-01 06:41:17 +00:00
pnpm-lock.yaml MCP Revamp (#1120) (#1380) 2026-07-30 17:08:51 -07:00
README.md fix mcp graph and file uploads (#1397) 2026-08-04 18:58:14 +00:00
tsconfig.json MCP Revamp (#1120) (#1380) 2026-07-30 17:08:51 -07:00
tsconfig.widget.json MCP Revamp (#1120) (#1380) 2026-07-30 17:08:51 -07:00
vite.config.dev.ts MCP Revamp (#1120) (#1380) 2026-07-30 17:08:51 -07:00
vite.config.ts fix chatgpt memory graph rendering (#1393) 2026-07-31 21:20:15 +00:00
vitest.config.ts MCP Revamp (#1120) (#1380) 2026-07-30 17:08:51 -07:00
wrangler.jsonc MCP Revamp (#1120) (#1380) 2026-07-30 17:08:51 -07:00

Supermemory MCP Server

The Supermemory MCP server gives authenticated AI clients access to a user's memories, profile, spaces, and interactive MCP Apps.

Runtime Model

  • MCP SDK v2 with a fresh McpServer for every HTTP request
  • Modern MCP 2026-07-28 plus stateless compatibility for 2025 clients
  • OAuth token validation on every request
  • No MCP protocol session or protocol Durable Object
  • Active space stored as application state in a dedicated Durable Object
  • Space state keyed by authenticated organizationId + userId

The space used by an operation resolves in this order:

  1. An explicit containerTag tool or prompt argument
  2. The account's durable active space
  3. The Supermemory client default, sm_project_default

An explicit override applies only to that call. It does not mutate the active space.

Server URL

https://mcp.supermemory.ai/mcp

Example client configuration:

{
  "mcpServers": {
    "supermemory": {
      "url": "https://mcp.supermemory.ai/mcp"
    }
  }
}

The client discovers the OAuth authorization server through /.well-known/oauth-protected-resource/mcp.

Tools

Model-visible tools

Tool Purpose
search_memory Search memories and optionally include profile context
listDocuments List document metadata and summaries in a space
getDocument Read one document's available content by ID
listMemories List extracted memory entries and their source document IDs
listSpaces List spaces visible to the authenticated account
whoAmI Return identity, access, and active-space context
add_memory Save or forget a memory

MCP App launchers

Tool Purpose
select-space Open the interactive space picker
memory-graph Open the interactive memory graph
guided-save Open the guided memory form
upload-file Open the file upload form

App-only tools

These tools are available to the embedded MCP App and hidden from the model.

Tool Purpose
set-active-tag Persist the selected active space
save-memory Submit the guided save form
prepare-file-upload Prepare a secure direct file upload
fetch-graph-data Fetch graph documents for the app

Resources And Prompt

Kind Name or URI Purpose
Resource supermemory://profile Profile facts in the effective space
Resource supermemory://spaces Visible spaces
Resource ui://supermemory/app-<sha256>.html Embedded MCP App bundle
Prompt context Profile and recent context for an optional space

The App resource and tool metadata include both current nested ui metadata and the legacy flat resource URI key while MCP Apps completes its SDK v2 migration. The Worker runtime does not import the SDK v1 Apps server helpers.

Development

Install from the repository root:

bun install

Run the local Worker:

cd apps/mcp
bun run dev

The portless URL is http://mcp.dev.supermemory.

Useful commands:

bun run build
bun run check-types
bun run test:unit
bun run test:e2e

Authenticated end-to-end tests use credentials captured by:

bun e2e/capture-oauth-token.ts

Without stored OAuth credentials, authenticated test groups skip. Public OAuth discovery and rejection tests still run.

Configuration

Variable Purpose Default
API_URL Supermemory API and OAuth issuer https://api.supermemory.ai
MCP_RESOURCE Expected OAuth audience https://mcp.supermemory.ai/mcp
ALLOWED_MCP_ORIGIN_HOSTNAMES Additional comma-separated browser origins Built-in host allowlist
POSTHOG_API_KEY Server-side MCP tool analytics project key Disabled
POSTHOG_HOST PostHog ingestion host https://us.i.posthog.com

Storage And Rollout

SpaceState stores only the active space's container tag. It never stores bearer tokens, MCP client identity, or protocol messages.

The old SupermemoryMCP class and binding remain inert for one rollout. This keeps the migration non-destructive and rollback-safe. A later deployment can delete the old protocol class after production traffic and rollback windows have been checked.