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, }),