From 2d920c12f0a827b27646e666955364f9aa3ef760 Mon Sep 17 00:00:00 2001
From: Prasanna A P <106952318+Prasanna721@users.noreply.github.com>
Date: Mon, 20 Jul 2026 20:32:31 -0700
Subject: [PATCH] make mcp oauth only
---
.github/workflows/claude-auto-fix-ci.yml | 17 +---------
.github/workflows/claude-code-review.yml | 13 +-------
.github/workflows/claude.yml | 13 +-------
README.md | 15 ---------
README.zh-CN.md | 15 ---------
apps/docs/supermemory-mcp/introduction.mdx | 6 ++--
apps/docs/supermemory-mcp/mcp.mdx | 22 +------------
apps/docs/supermemory-mcp/setup.mdx | 23 ++------------
apps/mcp/README.md | 36 +++++-----------------
apps/mcp/e2e/auth.test.ts | 4 +--
apps/mcp/e2e/discovery.test.ts | 4 +--
apps/mcp/e2e/graph.test.ts | 4 +--
apps/mcp/e2e/helpers.ts | 12 +++-----
apps/mcp/e2e/list-memories.test.ts | 4 +--
apps/mcp/e2e/memory.test.ts | 4 +--
apps/mcp/e2e/oauth.test.ts | 6 ++--
apps/mcp/e2e/root-scope.test.ts | 4 +--
apps/mcp/e2e/widgets.test.ts | 4 +--
apps/mcp/src/server/auth/index.test.ts | 21 +++++--------
apps/mcp/src/server/auth/index.ts | 20 ------------
apps/mcp/src/server/index.ts | 23 ++------------
21 files changed, 49 insertions(+), 221 deletions(-)
diff --git a/.github/workflows/claude-auto-fix-ci.yml b/.github/workflows/claude-auto-fix-ci.yml
index 246e94c1..bb1849a0 100644
--- a/.github/workflows/claude-auto-fix-ci.yml
+++ b/.github/workflows/claude-auto-fix-ci.yml
@@ -77,8 +77,6 @@ jobs:
Branch: ${{ github.event.workflow_run.head_branch }}
Repository: ${{ github.repository }}
- Check supermemory for similar past CI failures and fixes.
-
Fix the CI failures. Common fixes:
- Biome lint errors: Run `bun run format-lint` or `biome check --fix .`
- Type errors: Run `bun run check-types` and fix reported issues
@@ -87,21 +85,8 @@ jobs:
After fixing, commit the changes and push directly to the branch `${{ github.event.workflow_run.head_branch }}`.
Do NOT create a new PR — the fixes should be pushed to the existing PR branch.
- Save the fix pattern to supermemory for future reference.
-
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: |
--max-turns 20
--model claude-opus-4-5-20251101
- --allowedTools "Read,Write,Edit,Glob,Grep,Bash(*),WebSearch,WebFetch,Task,mcp__supermemory,mcp__github"
- --mcp-config '{
- "mcpServers": {
- "supermemory": {
- "type": "http",
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer ${{ secrets.SUPERMEMORY_API_KEY }}"
- }
- }
- }
- }'
+ --allowedTools "Read,Write,Edit,Glob,Grep,Bash(*),WebSearch,WebFetch,Task,mcp__github"
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
index 38dbdf9d..d998a9a3 100644
--- a/.github/workflows/claude-code-review.yml
+++ b/.github/workflows/claude-code-review.yml
@@ -48,18 +48,7 @@ jobs:
# Enable inline comments for specific issues
claude_args: |
--model claude-opus-4-5-20251101
- --allowedTools "Read,Write,Edit,Glob,Grep,Bash(*),WebSearch,WebFetch,Task,mcp__supermemory__*,mcp__github__*"
- --mcp-config '{
- "mcpServers": {
- "supermemory": {
- "type": "http",
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer ${{ secrets.SUPERMEMORY_API_KEY }}"
- }
- }
- }
- }'
+ --allowedTools "Read,Write,Edit,Glob,Grep,Bash(*),WebSearch,WebFetch,Task,mcp__github__*"
prompt: |
You are a senior engineer reviewing a pull request. Your job is to catch real bugs, security issues, and logic errors that a human reviewer might miss. You are NOT a linter — do not comment on style, naming, formatting, or minor nitpicks.
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
index c37d662d..c810ef16 100644
--- a/.github/workflows/claude.yml
+++ b/.github/workflows/claude.yml
@@ -67,15 +67,4 @@ jobs:
claude_args: |
--max-turns 15
--model claude-opus-4-5-20251101
- --allowedTools "Read,Write,Edit,Glob,Grep,Bash(*),WebSearch,WebFetch,Task,mcp__supermemory,mcp__github"
- --mcp-config '{
- "mcpServers": {
- "supermemory": {
- "type": "http",
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer ${{ secrets.SUPERMEMORY_API_KEY }}"
- }
- }
- }
- }'
+ --allowedTools "Read,Write,Edit,Glob,Grep,Bash(*),WebSearch,WebFetch,Task,mcp__github"
diff --git a/README.md b/README.md
index 36e862f6..12cd4d39 100644
--- a/README.md
+++ b/README.md
@@ -182,21 +182,6 @@ Add this to your MCP client config:
}
```
-Or use an API key instead of OAuth:
-
-```json
-{
- "mcpServers": {
- "supermemory": {
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer sm_your_api_key_here"
- }
- }
- }
-}
-```
-
---
## Build with Supermemory (API)
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 7cd710fb..0a9946c9 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -158,21 +158,6 @@ MCP 服务器开源——[查看源码](https://supermemory.ai/docs/supermemory-
}
```
-如果想用 API key 代替 OAuth:
-
-```json
-{
- "mcpServers": {
- "supermemory": {
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer sm_your_api_key_here"
- }
- }
- }
-}
-```
-
---
## 用 Supermemory API 构建
diff --git a/apps/docs/supermemory-mcp/introduction.mdx b/apps/docs/supermemory-mcp/introduction.mdx
index 51f097ed..17e71342 100644
--- a/apps/docs/supermemory-mcp/introduction.mdx
+++ b/apps/docs/supermemory-mcp/introduction.mdx
@@ -27,7 +27,6 @@ When users interact with any connected AI application, the system captures relev
### Key Features
- **OAuth Authentication** - Secure login through Supermemory accounts
-- **API Key Support** - Alternative authentication for automation and CI/CD
- **Persistent Memory** - Save and recall information across sessions
- **User Profiles** - Auto-generated profiles from stored memories
- **Project Scoping** - Organize memories by project with `x-sm-project` header
@@ -36,7 +35,7 @@ When users interact with any connected AI application, the system captures relev
1. User interacts with any MCP-compatible AI client
2. The client connects to `https://mcp.supermemory.ai/mcp`
-3. OAuth flow authenticates the user (or API key validates directly)
+3. OAuth flow authenticates the user
4. During conversations, relevant information is stored using the `memory` tool
5. When context is needed, the `recall` tool retrieves relevant memories
6. The AI assistant accesses this persistent context regardless of which platform is being used
@@ -45,8 +44,7 @@ When users interact with any connected AI application, the system captures relev
### Authentication Model
-- **OAuth by default** - Secure authentication through Supermemory accounts
-- **API key alternative** - Keys start with `sm_` for programmatic access
+- **OAuth required** - Secure authentication through Supermemory accounts
- **Session isolation** - Complete user data separation per account
### Privacy Features
diff --git a/apps/docs/supermemory-mcp/mcp.mdx b/apps/docs/supermemory-mcp/mcp.mdx
index f317d920..0ddd3900 100644
--- a/apps/docs/supermemory-mcp/mcp.mdx
+++ b/apps/docs/supermemory-mcp/mcp.mdx
@@ -28,26 +28,7 @@ Add to your MCP client config:
}
```
-The server uses **OAuth** by default. Your client will discover the authorization server via `/.well-known/oauth-protected-resource` and prompt you to authenticate.
-
-### API Key Authentication (Alternative)
-
-If you prefer API keys over OAuth, get one from [app.supermemory.ai](https://app.supermemory.ai) and pass it in the `Authorization` header:
-
-```json
-{
- "mcpServers": {
- "supermemory": {
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer sm_your_api_key_here"
- }
- }
- }
-}
-```
-
-API keys start with `sm_` and skip OAuth when provided.
+The server requires **OAuth**. Your client will discover the authorization server via `/.well-known/oauth-protected-resource` and prompt you to authenticate.
### Project Scoping
@@ -126,4 +107,3 @@ You can access this in Cursor and Claude Code by just doing /context, which will
View the open-source implementation
-
diff --git a/apps/docs/supermemory-mcp/setup.mdx b/apps/docs/supermemory-mcp/setup.mdx
index cf66e3a8..a6c10d60 100644
--- a/apps/docs/supermemory-mcp/setup.mdx
+++ b/apps/docs/supermemory-mcp/setup.mdx
@@ -26,26 +26,7 @@ Add this to your MCP client config (Claude Desktop, Cursor, Windsurf, etc.):
}
```
-The server uses **OAuth authentication** by default. Your MCP client will automatically discover the authorization server via `/.well-known/oauth-protected-resource` and prompt you to authenticate.
-
-## API Key Authentication (Alternative)
-
-If you prefer to use an API key instead of OAuth, get one from [app.supermemory.ai](https://app.supermemory.ai) and pass it in the `Authorization` header:
-
-```json
-{
- "mcpServers": {
- "supermemory": {
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer sm_your_api_key_here"
- }
- }
- }
-}
-```
-
-API keys start with `sm_` and are automatically detected. When an API key is provided, OAuth authentication is skipped.
+The server requires **OAuth authentication**. Your MCP client will automatically discover the authorization server via `/.well-known/oauth-protected-resource` and prompt you to authenticate.
## Project Scoping (Optional)
@@ -90,4 +71,4 @@ Or use the one-click install button at [app.supermemory.ai](https://app.supermem
### Windsurf / VS Code
-Configuration varies by extension. Generally, add the server URL (`https://mcp.supermemory.ai/mcp`) to your MCP settings.
\ No newline at end of file
+Configuration varies by extension. Generally, add the server URL (`https://mcp.supermemory.ai/mcp`) to your MCP settings.
diff --git a/apps/mcp/README.md b/apps/mcp/README.md
index ad396af9..ab9f3f3f 100644
--- a/apps/mcp/README.md
+++ b/apps/mcp/README.md
@@ -4,7 +4,7 @@ A standalone MCP (Model Context Protocol) server for Supermemory that gives AI a
## Features
-- **Authentication** - Supports both API keys and OAuth authentication
+- **Authentication** - OAuth 2.1 with dynamic client registration
- **Persistent Memory** - Save and recall information across sessions
- **User Profiles** - Auto-generated profiles from stored memories
- **Project Scoping** - Organize memories by project with `x-sm-project` header
@@ -34,26 +34,7 @@ Add to your MCP client config (Claude Desktop, Cursor, Windsurf, etc.):
}
```
-The server uses OAuth authentication by default. Your MCP client will automatically discover the authorization server via `/.well-known/oauth-protected-resource` and prompt you to authenticate.
-
-### API Key Authentication (Alternative)
-
-If you prefer to use an API key instead of OAuth, you can pass it directly in the `Authorization` header. Get your API key from [app.supermemory.ai](https://app.supermemory.ai):
-
-```json
-{
- "mcpServers": {
- "supermemory": {
- "url": "https://mcp.supermemory.ai/mcp",
- "headers": {
- "Authorization": "Bearer sm_your_api_key_here"
- }
- }
- }
-}
-```
-
-API keys start with `sm_` and are automatically detected. When an API key is provided, OAuth authentication is skipped.
+The server requires OAuth authentication. Your MCP client will automatically discover the authorization server via `/.well-known/oauth-protected-resource` and prompt you to authenticate.
### Project Scoping (Optional)
@@ -196,9 +177,9 @@ core journey: handshake → tool/resource/prompt discovery → `whoAmI` → `lis
the `context` prompt, container-tag isolation, and auth rejections.
```bash
-export SUPERMEMORY_API_KEY=sm_... # staging key (required; tests skip without it)
export SUPERMEMORY_MCP_URL=https://mcp.supermemory.ai/mcp # optional, this is the default
export SUPERMEMORY_API_URL=https://api.supermemory.ai # optional, OAuth authorization server
+bun e2e/capture-oauth-token.ts # one-time browser authorization
bun run test:e2e
```
@@ -220,8 +201,8 @@ API (`api.supermemory.ai`, better-auth). `oauth.test.ts` covers the real flow in
- **A–C (no secrets)** — discovery chain, dynamic client registration, and token/authorize
negatives. These exercise the protocol wiring with no key and no browser, so they always run.
- **D (real token)** — exchanges a seeded `refresh_token` for an `access_token` and connects to
- `/mcp` with it, exercising the OAuth-token validation path (not the `sm_` API-key path). It
- **skips** unless both env vars below are set.
+ `/mcp` with it, exercising the OAuth-token validation path. It **skips** unless both OAuth
+ credentials below are available.
```bash
# One-time capture (opens a browser for login + consent, prints the env vars):
@@ -231,8 +212,8 @@ export SUPERMEMORY_MCP_REFRESH_TOKEN=...
```
Notes:
-- Tests **skip** (not fail) without `SUPERMEMORY_API_KEY`; Tier D OAuth tests skip without the
- refresh-token env vars — so CI is safe without secrets.
+- Authenticated tests **skip** (not fail) without stored OAuth credentials or the refresh-token
+ environment variables, so CI is safe without secrets.
- `recall` is eventually-consistent (save → ingestion pipeline → memories), so the round-trip
**polls up to ~90s**. `forget` removal is slower still and is asserted as best-effort.
- The suite uses unique per-run markers and forgets them in teardown to avoid polluting the account.
@@ -246,7 +227,7 @@ bun run deploy
## Architecture
```
-┌─────────────────┐ OAuth/API Key ┌──────────────────┐
+┌─────────────────┐ OAuth ┌──────────────────┐
│ MCP Client │◄──────────────►│ Supermemory API │
│ (Claude, Cursor)│ │ (api.supermemory.ai)
└────────┬────────┘ └──────────────────┘
@@ -273,4 +254,3 @@ bun run deploy
- **MCP SDK:** @modelcontextprotocol/sdk + agents
- **API Client:** supermemory SDK
- **Analytics:** PostHog
-
diff --git a/apps/mcp/e2e/auth.test.ts b/apps/mcp/e2e/auth.test.ts
index fac04c49..d4cf0da1 100644
--- a/apps/mcp/e2e/auth.test.ts
+++ b/apps/mcp/e2e/auth.test.ts
@@ -18,7 +18,7 @@ const mcpHeaders = (auth?: string) => ({
...(auth ? { Authorization: auth } : {}),
})
-// No API key needed — exercises the public surface and auth rejections.
+// No credentials needed — exercises the public surface and auth rejections.
describe("MCP — transport & auth (raw HTTP)", () => {
it("GET / returns service info", async () => {
const res = await fetch(`${ORIGIN}/`)
@@ -52,7 +52,7 @@ describe("MCP — transport & auth (raw HTTP)", () => {
expect(res.headers.get("www-authenticate")).toMatch(/Bearer/)
})
- it("rejects an invalid API key (401 with JSON-RPC error)", async () => {
+ it("rejects an opaque API key as an invalid OAuth token", async () => {
const res = await fetch(MCP_URL, {
method: "POST",
headers: mcpHeaders("Bearer sm_invalid_key_for_e2e"),
diff --git a/apps/mcp/e2e/discovery.test.ts b/apps/mcp/e2e/discovery.test.ts
index e837416b..8ca5bd64 100644
--- a/apps/mcp/e2e/discovery.test.ts
+++ b/apps/mcp/e2e/discovery.test.ts
@@ -1,6 +1,6 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import {
- AUTH_CREDENTIALS_AVAILABLE,
+ OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
textOf,
@@ -14,7 +14,7 @@ const EXPECTED_TOOLS = [
"whoAmI",
"memory-graph",
]
-const describeWithAuth = describe.skipIf(!AUTH_CREDENTIALS_AVAILABLE)
+const describeWithAuth = describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)
describeWithAuth("MCP — discovery & identity", () => {
let s: Session
diff --git a/apps/mcp/e2e/graph.test.ts b/apps/mcp/e2e/graph.test.ts
index 33fc12b9..5a11a9f3 100644
--- a/apps/mcp/e2e/graph.test.ts
+++ b/apps/mcp/e2e/graph.test.ts
@@ -1,12 +1,12 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import {
- AUTH_CREDENTIALS_AVAILABLE,
+ OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
type Session,
textOf,
} from "./helpers"
-const describeWithAuth = describe.skipIf(!AUTH_CREDENTIALS_AVAILABLE)
+const describeWithAuth = describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)
describeWithAuth("MCP — graph, resources & prompts", () => {
let s: Session
diff --git a/apps/mcp/e2e/helpers.ts b/apps/mcp/e2e/helpers.ts
index 0baa5066..e3dcfe67 100644
--- a/apps/mcp/e2e/helpers.ts
+++ b/apps/mcp/e2e/helpers.ts
@@ -12,7 +12,6 @@ import { fileURLToPath } from "node:url"
export const MCP_URL =
process.env.SUPERMEMORY_MCP_URL ?? "https://mcp.supermemory.ai/mcp"
-export const API_KEY = process.env.SUPERMEMORY_API_KEY
export const ORIGIN = new URL(MCP_URL).origin
export const API_URL =
process.env.SUPERMEMORY_API_URL ?? "https://api.supermemory.ai"
@@ -60,16 +59,15 @@ export const OAUTH_REFRESH_TOKEN =
export const OAUTH_CLIENT_ID =
process.env.SUPERMEMORY_MCP_CLIENT_ID ??
storedCredentials.SUPERMEMORY_MCP_CLIENT_ID
-export const AUTH_CREDENTIALS_AVAILABLE = Boolean(
- API_KEY || (OAUTH_REFRESH_TOKEN && OAUTH_CLIENT_ID),
+export const OAUTH_CREDENTIALS_AVAILABLE = Boolean(
+ OAUTH_REFRESH_TOKEN && OAUTH_CLIENT_ID,
)
let defaultOAuthAccessToken: Promise | undefined
async function defaultBearerToken(): Promise {
- if (API_KEY) return API_KEY
if (!OAUTH_REFRESH_TOKEN || !OAUTH_CLIENT_ID) {
- throw new Error("No API key or OAuth test credentials configured")
+ throw new Error("No OAuth test credentials configured")
}
defaultOAuthAccessToken ??= (async () => {
@@ -185,9 +183,9 @@ export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
export type Session = { client: Client; close: () => Promise }
export async function connect(
- opts: { apiKey?: string; token?: string; containerTag?: string } = {},
+ opts: { token?: string; containerTag?: string } = {},
): Promise {
- const bearerToken = opts.token ?? opts.apiKey ?? (await defaultBearerToken())
+ const bearerToken = opts.token ?? (await defaultBearerToken())
const headers: Record = {
Authorization: `Bearer ${bearerToken}`,
}
diff --git a/apps/mcp/e2e/list-memories.test.ts b/apps/mcp/e2e/list-memories.test.ts
index 42088203..fbaeb263 100644
--- a/apps/mcp/e2e/list-memories.test.ts
+++ b/apps/mcp/e2e/list-memories.test.ts
@@ -1,13 +1,13 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import {
- AUTH_CREDENTIALS_AVAILABLE,
+ OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
type Session,
textOf,
} from "./helpers"
-describe.skipIf(!AUTH_CREDENTIALS_AVAILABLE)("MCP — listMemories", () => {
+describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)("MCP — listMemories", () => {
let s: Session
beforeAll(async () => {
diff --git a/apps/mcp/e2e/memory.test.ts b/apps/mcp/e2e/memory.test.ts
index f905127c..7ff5664a 100644
--- a/apps/mcp/e2e/memory.test.ts
+++ b/apps/mcp/e2e/memory.test.ts
@@ -1,7 +1,7 @@
import { randomUUID } from "node:crypto"
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import {
- AUTH_CREDENTIALS_AVAILABLE,
+ OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
recallUntil,
@@ -9,7 +9,7 @@ import {
textOf,
} from "./helpers"
-describe.skipIf(!AUTH_CREDENTIALS_AVAILABLE)("MCP — memory behaviors", () => {
+describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)("MCP — memory behaviors", () => {
let s: Session
const created: Array<{ content: string; containerTag?: string }> = []
diff --git a/apps/mcp/e2e/oauth.test.ts b/apps/mcp/e2e/oauth.test.ts
index 9f2b3e28..ba2eaca0 100644
--- a/apps/mcp/e2e/oauth.test.ts
+++ b/apps/mcp/e2e/oauth.test.ts
@@ -98,7 +98,7 @@ describe("MCP — OAuth protocol (no secrets)", () => {
})
})
-// Tier D — real OAuth token through /mcp, exercising validateOAuthToken (not the sm_ branch); needs a seeded refresh token.
+// Tier D — real OAuth token through /mcp; needs a seeded refresh token.
describe.skipIf(!OAUTH_REFRESH_TOKEN || !OAUTH_CLIENT_ID)(
"MCP — real OAuth token round-trip",
() => {
@@ -122,8 +122,8 @@ describe.skipIf(!OAUTH_REFRESH_TOKEN || !OAUTH_CLIENT_ID)(
await s?.close()
})
- it("mints an OAuth access token that is not an sm_ API key", () => {
- expect(accessToken.startsWith("sm_")).toBe(false)
+ it("mints a JWT access token for the MCP resource", () => {
+ expect(accessToken.split(".")).toHaveLength(3)
})
it("connects to /mcp with the OAuth token and resolves identity", async () => {
diff --git a/apps/mcp/e2e/root-scope.test.ts b/apps/mcp/e2e/root-scope.test.ts
index 8fe9a3c7..187730eb 100644
--- a/apps/mcp/e2e/root-scope.test.ts
+++ b/apps/mcp/e2e/root-scope.test.ts
@@ -1,7 +1,7 @@
import { randomUUID } from "node:crypto"
import { describe, expect, it } from "vitest"
import {
- AUTH_CREDENTIALS_AVAILABLE,
+ OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
recallUntil,
@@ -18,7 +18,7 @@ const propsOf = (tools: ToolLike[], name: string): Record =>
// Fixed tag (not a per-run UUID) so the test doesn't mint a new project each run.
const SCOPE_TAG = "sm_e2e_root"
-const describeWithAuth = describe.skipIf(!AUTH_CREDENTIALS_AVAILABLE)
+const describeWithAuth = describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)
// x-sm-project locks the connection to one project: strips containerTag from schemas and scopes every op — distinct from the per-call arg.
describeWithAuth("MCP — x-sm-project root scoping", () => {
diff --git a/apps/mcp/e2e/widgets.test.ts b/apps/mcp/e2e/widgets.test.ts
index 3801c252..0966a449 100644
--- a/apps/mcp/e2e/widgets.test.ts
+++ b/apps/mcp/e2e/widgets.test.ts
@@ -1,12 +1,12 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import {
- AUTH_CREDENTIALS_AVAILABLE,
+ OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
type Session,
} from "./helpers"
-describe.skipIf(!AUTH_CREDENTIALS_AVAILABLE)(
+describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)(
"MCP - on-demand widget permissions",
() => {
let session: Session
diff --git a/apps/mcp/src/server/auth/index.test.ts b/apps/mcp/src/server/auth/index.test.ts
index 9a0de41c..4ec23035 100644
--- a/apps/mcp/src/server/auth/index.test.ts
+++ b/apps/mcp/src/server/auth/index.test.ts
@@ -1,6 +1,6 @@
import { createLocalJWKSet, exportJWK, generateKeyPair, SignJWT } from "jose"
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"
-import { fetchSession, validateApiKey, validateOAuthToken } from "./index"
+import { fetchSession, validateOAuthToken } from "./index"
const API_URL = "https://api.example.com"
const ISSUER = `${API_URL}/api/auth`
@@ -73,20 +73,15 @@ describe("MCP authentication", () => {
).resolves.toBeNull()
})
- it("introspects opaque API keys through v3/session", async () => {
- const fetchSpy = vi.fn().mockResolvedValue(
- new Response(JSON.stringify({ user: { id: "user_api_key" } }), {
- status: 200,
- }),
- )
+ it("rejects opaque API keys without an API request", async () => {
+ vi.spyOn(console, "error").mockImplementation(() => {})
+ const fetchSpy = vi.fn()
vi.stubGlobal("fetch", fetchSpy)
- await expect(validateApiKey("sm_test", `${API_URL}/`)).resolves.toEqual({
- userId: "user_api_key",
- bearerToken: "sm_test",
- })
- expect(fetchSpy).toHaveBeenCalledOnce()
- expect(fetchSpy.mock.calls[0][0]).toBe(`${API_URL}/v3/session`)
+ await expect(
+ validateOAuthToken("sm_test", API_URL, MCP_RESOURCE, keySet),
+ ).resolves.toBeNull()
+ expect(fetchSpy).not.toHaveBeenCalled()
})
it("surfaces on-demand session failures to the calling tool", async () => {
diff --git a/apps/mcp/src/server/auth/index.ts b/apps/mcp/src/server/auth/index.ts
index 9eb7316d..58a1e3b0 100644
--- a/apps/mcp/src/server/auth/index.ts
+++ b/apps/mcp/src/server/auth/index.ts
@@ -10,10 +10,6 @@ export interface AuthUser {
const remoteJwks = new Map>()
-export function isApiKey(token: string): boolean {
- return token.startsWith("sm_")
-}
-
function authIssuer(apiUrl: string): string {
return `${apiUrl.replace(/\/+$/, "")}/api/auth`
}
@@ -52,22 +48,6 @@ export async function fetchSession(
return session
}
-export async function validateApiKey(
- apiKey: string,
- apiUrl: string,
-): Promise {
- try {
- const session = await fetchSession(apiKey, apiUrl)
- return {
- userId: session.user.id,
- bearerToken: apiKey,
- }
- } catch (error) {
- console.error("API key validation error:", error)
- return null
- }
-}
-
export async function validateOAuthToken(
token: string,
apiUrl: string,
diff --git a/apps/mcp/src/server/index.ts b/apps/mcp/src/server/index.ts
index 21f897ff..746d78c5 100644
--- a/apps/mcp/src/server/index.ts
+++ b/apps/mcp/src/server/index.ts
@@ -3,12 +3,7 @@ import { cors } from "hono/cors"
import type { ContentfulStatusCode } from "hono/utils/http-status"
import type { Props } from "../shared/types"
import { SupermemoryMCP } from "./agent"
-import {
- type AuthUser,
- isApiKey,
- validateApiKey,
- validateOAuthToken,
-} from "./auth"
+import { validateOAuthToken } from "./auth"
type Bindings = {
MCP_SERVER: DurableObjectNamespace
@@ -16,16 +11,6 @@ type Bindings = {
MCP_RESOURCE?: string
}
-async function resolveAuth(
- token: string,
- apiUrl: string,
- mcpResource: string,
-): Promise {
- return isApiKey(token)
- ? await validateApiKey(token, apiUrl)
- : await validateOAuthToken(token, apiUrl, mcpResource)
-}
-
export type { Props }
const app = new Hono<{ Bindings: Bindings }>()
@@ -141,7 +126,7 @@ async function handleMcpRequest(
})
}
- const authUser = await resolveAuth(token, apiUrl, mcpResource)
+ const authUser = await validateOAuthToken(token, apiUrl, mcpResource)
if (!authUser) {
return new Response(
@@ -149,9 +134,7 @@ async function handleMcpRequest(
jsonrpc: "2.0",
error: {
code: -32000,
- message: isApiKey(token)
- ? "Invalid or expired API key"
- : "Invalid or expired token",
+ message: "Invalid or expired token",
},
id: null,
}),