From bfd3f21b770c931897a0037fb7d2499bc1a162be Mon Sep 17 00:00:00 2001 From: hhhjin Date: Sat, 11 Apr 2026 15:34:27 +0900 Subject: [PATCH] remove OAuth and MCP support --- README.md | 1 - client/lib/i18nResources.ts | 99 --- client/lib/oauthSignedQuery.spec.ts | 23 - client/lib/oauthSignedQuery.ts | 21 - client/routeTree.gen.ts | 64 +- client/routes/oauth.consent.tsx | 111 --- client/routes/oauth.login.tsx | 101 --- docs/README.ko.md | 1 - docs/README.zh-CN.md | 1 - migrations/0005_tranquil_imperial_guard.sql | 5 + migrations/meta/0005_snapshot.json | 668 ++++++++++++++++++ migrations/meta/_journal.json | 7 + package.json | 3 - pnpm-lock.yaml | 714 +------------------- server/db/schema/auth-schema.ts | 220 ------ server/index.ts | 4 - server/lib/auth.ts | 35 +- server/mcp/auth.ts | 225 ------ server/mcp/constants.ts | 17 - server/mcp/server.ts | 432 ------------ server/routes/auth.route.ts | 62 +- server/routes/mcp.route.ts | 159 ----- server/routes/oauth-ui.route.ts | 11 - test/auth-schema.oauth-json.test.ts | 95 --- test/helpers/mcp.ts | 124 ---- test/helpers/seed.ts | 30 - test/mcp.remote.test.ts | 700 ------------------- 27 files changed, 693 insertions(+), 3240 deletions(-) delete mode 100644 client/lib/oauthSignedQuery.spec.ts delete mode 100644 client/lib/oauthSignedQuery.ts delete mode 100644 client/routes/oauth.consent.tsx delete mode 100644 client/routes/oauth.login.tsx create mode 100644 migrations/0005_tranquil_imperial_guard.sql create mode 100644 migrations/meta/0005_snapshot.json delete mode 100644 server/mcp/auth.ts delete mode 100644 server/mcp/constants.ts delete mode 100644 server/mcp/server.ts delete mode 100644 server/routes/mcp.route.ts delete mode 100644 server/routes/oauth-ui.route.ts delete mode 100644 test/auth-schema.oauth-json.test.ts delete mode 100644 test/helpers/mcp.ts delete mode 100644 test/mcp.remote.test.ts diff --git a/README.md b/README.md index 6f2df7b..8d3bcb3 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Transform your Markdown files into beautifully rendered web pages instantly. A f ## Roadmap - [x] **Public API**: Add a documented programmatic interface for uploads, page management, and third-party integrations. -- [ ] **MCP server**: Add an MCP integration surface for AI agents and tooling workflows that need to work with mdto programmatically. - [ ] **Image hosting for Markdown**: Upload images referenced in Markdown to mdto automatically, so you do not need to host them somewhere else first. ## Tech Stack diff --git a/client/lib/i18nResources.ts b/client/lib/i18nResources.ts index 6c2301f..0c19054 100644 --- a/client/lib/i18nResources.ts +++ b/client/lib/i18nResources.ts @@ -106,31 +106,6 @@ export const resources = { description: "Welcome back! Please log in to continue.", continueWithGithub: "Continue with GitHub", }, - oauth: { - login: { - title: "Sign in to continue", - description: "mdto MCP access requires an authenticated account.", - continueWithGithub: "Continue with GitHub", - continuing: "Continuing OAuth authorization...", - redirecting: "Signed in. Redirecting...", - errors: { - continueAuthorization: "Failed to continue OAuth authorization", - missingRedirectUrl: "Missing OAuth redirect URL", - }, - }, - consent: { - title: "Authorize mdto access", - description: - "Client {{clientId}} is requesting access to your mdto account.", - requestedScopes: "Requested scopes", - deny: "Deny", - allow: "Allow", - errors: { - processConsent: "Failed to process OAuth consent", - missingRedirectUrl: "Missing OAuth redirect URL", - }, - }, - }, dashboard: { title: "Dashboard", filter: { @@ -318,31 +293,6 @@ export const resources = { description: "돌아오신 것을 환영해요! 계속하려면 로그인하세요.", continueWithGithub: "GitHub로 계속하기", }, - oauth: { - login: { - title: "계속하려면 로그인", - description: "mdto MCP 액세스에는 인증된 계정이 필요해요.", - continueWithGithub: "GitHub로 계속하기", - continuing: "OAuth 인증을 계속하는 중...", - redirecting: "로그인됨. 이동하는 중...", - errors: { - continueAuthorization: "OAuth 인증을 계속하지 못했어요", - missingRedirectUrl: "OAuth 리디렉션 URL이 없어요", - }, - }, - consent: { - title: "mdto 접근 권한 승인", - description: - "클라이언트 {{clientId}} 이(가) mdto 계정 접근 권한을 요청하고 있어요.", - requestedScopes: "요청된 권한", - deny: "거부", - allow: "허용", - errors: { - processConsent: "OAuth 동의 처리에 실패했어요", - missingRedirectUrl: "OAuth 리디렉션 URL이 없어요", - }, - }, - }, dashboard: { title: "대시보드", filter: { @@ -528,30 +478,6 @@ export const resources = { description: "欢迎回来!请登录以继续。", continueWithGithub: "使用 GitHub 继续", }, - oauth: { - login: { - title: "登录以继续", - description: "mdto MCP 访问需要已验证的账户。", - continueWithGithub: "使用 GitHub 继续", - continuing: "正在继续 OAuth 授权...", - redirecting: "已登录,正在跳转...", - errors: { - continueAuthorization: "无法继续 OAuth 授权", - missingRedirectUrl: "缺少 OAuth 重定向 URL", - }, - }, - consent: { - title: "授权 mdto 访问", - description: "客户端 {{clientId}} 正在请求访问你的 mdto 账户。", - requestedScopes: "请求的权限", - deny: "拒绝", - allow: "允许", - errors: { - processConsent: "处理 OAuth 同意时失败", - missingRedirectUrl: "缺少 OAuth 重定向 URL", - }, - }, - }, dashboard: { title: "仪表盘", filter: { @@ -733,31 +659,6 @@ export const resources = { description: "おかえりなさい。続行するにはログインしてください。", continueWithGithub: "GitHub で続ける", }, - oauth: { - login: { - title: "続行するにはログイン", - description: "mdto MCP を利用するには認証済みアカウントが必要です。", - continueWithGithub: "GitHub で続ける", - continuing: "OAuth 認可を続行しています...", - redirecting: "ログイン済みです。リダイレクトしています...", - errors: { - continueAuthorization: "OAuth 認可を続行できませんでした", - missingRedirectUrl: "OAuth リダイレクト URL がありません", - }, - }, - consent: { - title: "mdto へのアクセスを許可", - description: - "クライアント {{clientId}} が mdto アカウントへのアクセスを要求しています。", - requestedScopes: "要求されたスコープ", - deny: "拒否", - allow: "許可", - errors: { - processConsent: "OAuth 同意の処理に失敗しました", - missingRedirectUrl: "OAuth リダイレクト URL がありません", - }, - }, - }, dashboard: { title: "ダッシュボード", filter: { diff --git a/client/lib/oauthSignedQuery.spec.ts b/client/lib/oauthSignedQuery.spec.ts deleted file mode 100644 index 4c2cd24..0000000 --- a/client/lib/oauthSignedQuery.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { getSignedOAuthQuery } from "./oauthSignedQuery"; - -describe("getSignedOAuthQuery", () => { - it("returns the full query when no signature is present", () => { - expect(getSignedOAuthQuery("?client_id=test&scope=openid")).toBe( - "client_id=test&scope=openid", - ); - }); - - it("keeps only the signed portion of the query", () => { - expect( - getSignedOAuthQuery( - "?client_id=test&scope=openid&exp=123&sig=abc123&utm_source=newsletter", - ), - ).toBe("client_id=test&scope=openid&exp=123&sig=abc123"); - }); - - it("returns an empty string for an empty search string", () => { - expect(getSignedOAuthQuery("")).toBe(""); - expect(getSignedOAuthQuery("?")).toBe(""); - }); -}); diff --git a/client/lib/oauthSignedQuery.ts b/client/lib/oauthSignedQuery.ts deleted file mode 100644 index 08f0355..0000000 --- a/client/lib/oauthSignedQuery.ts +++ /dev/null @@ -1,21 +0,0 @@ -export function getSignedOAuthQuery(search: string): string { - const normalizedSearch = search.startsWith("?") ? search.slice(1) : search; - if (!normalizedSearch) { - return ""; - } - - const params = new URLSearchParams(normalizedSearch); - if (!params.has("sig")) { - return params.toString(); - } - - const signedParams = new URLSearchParams(); - for (const [key, value] of params.entries()) { - signedParams.append(key, value); - if (key === "sig") { - break; - } - } - - return signedParams.toString(); -} diff --git a/client/routeTree.gen.ts b/client/routeTree.gen.ts index 602c700..9b382a4 100644 --- a/client/routeTree.gen.ts +++ b/client/routeTree.gen.ts @@ -14,8 +14,6 @@ import { Route as PrivacyRouteImport } from './routes/privacy' import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as UsernameRouteImport } from './routes/$username' import { Route as IndexRouteImport } from './routes/index' -import { Route as OauthLoginRouteImport } from './routes/oauth.login' -import { Route as OauthConsentRouteImport } from './routes/oauth.consent' const TermsRoute = TermsRouteImport.update({ id: '/terms', @@ -42,16 +40,6 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const OauthLoginRoute = OauthLoginRouteImport.update({ - id: '/oauth/login', - path: '/oauth/login', - getParentRoute: () => rootRouteImport, -} as any) -const OauthConsentRoute = OauthConsentRouteImport.update({ - id: '/oauth/consent', - path: '/oauth/consent', - getParentRoute: () => rootRouteImport, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -59,8 +47,6 @@ export interface FileRoutesByFullPath { '/dashboard': typeof DashboardRoute '/privacy': typeof PrivacyRoute '/terms': typeof TermsRoute - '/oauth/consent': typeof OauthConsentRoute - '/oauth/login': typeof OauthLoginRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -68,8 +54,6 @@ export interface FileRoutesByTo { '/dashboard': typeof DashboardRoute '/privacy': typeof PrivacyRoute '/terms': typeof TermsRoute - '/oauth/consent': typeof OauthConsentRoute - '/oauth/login': typeof OauthLoginRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -78,37 +62,13 @@ export interface FileRoutesById { '/dashboard': typeof DashboardRoute '/privacy': typeof PrivacyRoute '/terms': typeof TermsRoute - '/oauth/consent': typeof OauthConsentRoute - '/oauth/login': typeof OauthLoginRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/' - | '/$username' - | '/dashboard' - | '/privacy' - | '/terms' - | '/oauth/consent' - | '/oauth/login' + fullPaths: '/' | '/$username' | '/dashboard' | '/privacy' | '/terms' fileRoutesByTo: FileRoutesByTo - to: - | '/' - | '/$username' - | '/dashboard' - | '/privacy' - | '/terms' - | '/oauth/consent' - | '/oauth/login' - id: - | '__root__' - | '/' - | '/$username' - | '/dashboard' - | '/privacy' - | '/terms' - | '/oauth/consent' - | '/oauth/login' + to: '/' | '/$username' | '/dashboard' | '/privacy' | '/terms' + id: '__root__' | '/' | '/$username' | '/dashboard' | '/privacy' | '/terms' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -117,8 +77,6 @@ export interface RootRouteChildren { DashboardRoute: typeof DashboardRoute PrivacyRoute: typeof PrivacyRoute TermsRoute: typeof TermsRoute - OauthConsentRoute: typeof OauthConsentRoute - OauthLoginRoute: typeof OauthLoginRoute } declare module '@tanstack/react-router' { @@ -158,20 +116,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/oauth/login': { - id: '/oauth/login' - path: '/oauth/login' - fullPath: '/oauth/login' - preLoaderRoute: typeof OauthLoginRouteImport - parentRoute: typeof rootRouteImport - } - '/oauth/consent': { - id: '/oauth/consent' - path: '/oauth/consent' - fullPath: '/oauth/consent' - preLoaderRoute: typeof OauthConsentRouteImport - parentRoute: typeof rootRouteImport - } } } @@ -181,8 +125,6 @@ const rootRouteChildren: RootRouteChildren = { DashboardRoute: DashboardRoute, PrivacyRoute: PrivacyRoute, TermsRoute: TermsRoute, - OauthConsentRoute: OauthConsentRoute, - OauthLoginRoute: OauthLoginRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/client/routes/oauth.consent.tsx b/client/routes/oauth.consent.tsx deleted file mode 100644 index 1cb2c3e..0000000 --- a/client/routes/oauth.consent.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { getSignedOAuthQuery } from "../lib/oauthSignedQuery"; - -export const Route = createFileRoute("/oauth/consent")({ - component: OAuthConsentPage, -}); - -type ConsentResponse = { - redirect_uri?: string; - url?: string; -}; - -function OAuthConsentPage() { - const { t } = useTranslation(); - const [error, setError] = useState(null); - const [isSubmitting, setIsSubmitting] = useState(false); - const [oauthQuery, setOauthQuery] = useState(""); - const [requestedScopes, setRequestedScopes] = useState([]); - const [clientId, setClientId] = useState(null); - - useEffect(() => { - const search = window.location.search; - const searchParams = new URLSearchParams(search); - - setOauthQuery(getSignedOAuthQuery(search)); - setRequestedScopes( - (searchParams.get("scope") ?? "").split(" ").filter(Boolean), - ); - setClientId(searchParams.get("client_id")); - }, []); - - const submitConsent = async (accept: boolean) => { - setError(null); - setIsSubmitting(true); - - try { - const response = await fetch("/api/auth/oauth2/consent", { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify({ - accept, - oauth_query: oauthQuery, - }), - }); - const payload = (await response.json()) as ConsentResponse; - if (!response.ok) { - throw new Error(t("oauth.consent.errors.processConsent")); - } - - const redirectUrl = payload.redirect_uri ?? payload.url; - if (!redirectUrl) { - throw new Error(t("oauth.consent.errors.missingRedirectUrl")); - } - - window.location.assign(redirectUrl); - } catch (caughtError: unknown) { - setError( - caughtError instanceof Error - ? caughtError.message - : t("oauth.consent.errors.processConsent"), - ); - setIsSubmitting(false); - } - }; - - return ( -
-
-

{t("oauth.consent.title")}

-

- {t("oauth.consent.description", { - clientId: clientId ?? "unknown", - })} -

-
-

- {t("oauth.consent.requestedScopes")} -

-
    - {requestedScopes.map((scope) => ( -
  • {scope}
  • - ))} -
-
- {error ?

{error}

: null} -
- - -
-
-
- ); -} diff --git a/client/routes/oauth.login.tsx b/client/routes/oauth.login.tsx deleted file mode 100644 index 8f7140f..0000000 --- a/client/routes/oauth.login.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { authClient } from "../lib/auth-client"; -import { getSignedOAuthQuery } from "../lib/oauthSignedQuery"; - -export const Route = createFileRoute("/oauth/login")({ - component: OAuthLoginPage, -}); - -type ContinueResponse = { - redirect_uri?: string; - url?: string; -}; - -function OAuthLoginPage() { - const { t } = useTranslation(); - const { data: session, isPending } = authClient.useSession(); - const [error, setError] = useState(null); - const [isContinuing, setIsContinuing] = useState(false); - const [oauthQuery, setOauthQuery] = useState(""); - - useEffect(() => { - setOauthQuery(getSignedOAuthQuery(window.location.search)); - }, []); - - useEffect(() => { - if (!session?.user || !oauthQuery || isContinuing) { - return; - } - - setIsContinuing(true); - void fetch("/api/auth/oauth2/continue", { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify({ - postLogin: true, - oauth_query: oauthQuery, - }), - }) - .then(async (response) => { - const payload = (await response.json()) as ContinueResponse; - if (!response.ok) { - throw new Error(t("oauth.login.errors.continueAuthorization")); - } - - const redirectUrl = payload.redirect_uri ?? payload.url; - if (!redirectUrl) { - throw new Error(t("oauth.login.errors.missingRedirectUrl")); - } - - window.location.assign(redirectUrl); - }) - .catch((caughtError: unknown) => { - setError( - caughtError instanceof Error - ? caughtError.message - : t("oauth.login.errors.continueAuthorization"), - ); - setIsContinuing(false); - }); - }, [isContinuing, oauthQuery, session?.user, t]); - - const handleGithubLogin = async () => { - setError(null); - await authClient.signIn.social({ - provider: "github", - callbackURL: window.location.href, - }); - }; - - return ( -
-
-

{t("oauth.login.title")}

-

- {t("oauth.login.description")} -

- {error ?

{error}

: null} - {session?.user ? ( -

- {isContinuing - ? t("oauth.login.continuing") - : t("oauth.login.redirecting")} -

- ) : ( - - )} -
-
- ); -} diff --git a/docs/README.ko.md b/docs/README.ko.md index fa7c636..dc45aff 100644 --- a/docs/README.ko.md +++ b/docs/README.ko.md @@ -20,7 +20,6 @@ ## 로드맵 - [ ] **공개 API**: 업로드, 페이지 관리, 외부 서비스 연동을 위한 문서화된 프로그래밍 인터페이스를 추가할 예정입니다. -- [ ] **MCP 서버**: AI 에이전트와 각종 도구 워크플로에서 mdto를 프로그래밍 방식으로 다룰 수 있도록 MCP 기반 통합 지점을 추가할 예정입니다. - [ ] **마크다운 이미지 자동 업로드**: 마크다운 문서에 포함된 이미지를 mdto에 자동으로 업로드해서, 사용자가 이미지를 따로 외부에 호스팅하지 않아도 되게 할 예정입니다. ## 기술 스택 diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 094c1d7..44b4c54 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -20,7 +20,6 @@ ## 路线图 - [ ] **公共 API**: 计划提供一套文档化的程序接口,用于上传、页面管理以及第三方集成。 -- [ ] **MCP 服务器**: 计划提供面向 AI 代理和工具工作流的 MCP 集成入口,让 mdto 可以被程序化使用。 - [ ] **Markdown 图片自动托管**: 计划将 Markdown 中引用的图片自动上传到 mdto,这样用户就不需要先把图片托管到别的地方。 ## 技术栈 diff --git a/migrations/0005_tranquil_imperial_guard.sql b/migrations/0005_tranquil_imperial_guard.sql new file mode 100644 index 0000000..4cb8e5f --- /dev/null +++ b/migrations/0005_tranquil_imperial_guard.sql @@ -0,0 +1,5 @@ +DROP TABLE `oauth_access_token`;--> statement-breakpoint +DROP TABLE `oauth_consent`;--> statement-breakpoint +DROP TABLE `oauth_refresh_token`;--> statement-breakpoint +DROP TABLE `oauth_client`;--> statement-breakpoint +DROP TABLE `jwks`; diff --git a/migrations/meta/0005_snapshot.json b/migrations/meta/0005_snapshot.json new file mode 100644 index 0000000..48cd6c6 --- /dev/null +++ b/migrations/meta/0005_snapshot.json @@ -0,0 +1,668 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f6cb05b4-2d66-4425-9a16-56507dc9793b", + "prevId": "fb095011-3b28-4399-a20a-24de05ba4e4e", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "apikey": { + "name": "apikey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_request": { + "name": "last_request", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "apikey_configId_idx": { + "name": "apikey_configId_idx", + "columns": ["config_id"], + "isUnique": false + }, + "apikey_key_idx": { + "name": "apikey_key_idx", + "columns": ["key"], + "isUnique": false + }, + "apikey_referenceId_idx": { + "name": "apikey_referenceId_idx", + "columns": ["reference_id"], + "isUnique": false + } + }, + "foreignKeys": { + "apikey_reference_id_user_id_fk": { + "name": "apikey_reference_id_user_id_fk", + "tableFrom": "apikey", + "tableTo": "user", + "columnsFrom": ["reference_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": ["token"], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_dashboard_public": { + "name": "is_dashboard_public", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_name_unique": { + "name": "user_name_unique", + "columns": ["name"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": ["identifier"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page": { + "name": "page", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "page_userId_slug_idx": { + "name": "page_userId_slug_idx", + "columns": ["user_id", "slug"], + "isUnique": true, + "where": "\"page\".\"deleted_at\" is null" + }, + "page_expiresAt_idx": { + "name": "page_expiresAt_idx", + "columns": ["expires_at"], + "isUnique": false, + "where": "\"page\".\"deleted_at\" is null and \"page\".\"expires_at\" is not null" + } + }, + "foreignKeys": { + "page_user_id_user_id_fk": { + "name": "page_user_id_user_id_fk", + "tableFrom": "page", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 87d5ea6..ed92891 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1773899798916, "tag": "0004_moaning_omega_flight", "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1775885972700, + "tag": "0005_tranquil_imperial_guard", + "breakpoints": true } ] } diff --git a/package.json b/package.json index 18d11c9..557f865 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,9 @@ "dependencies": { "@base-ui/react": "^1.1.0", "@better-auth/api-key": "^1.5.5", - "@better-auth/oauth-provider": "^1.5.5", "@flowershow/remark-wiki-link": "^3.3.1", - "@hono/mcp": "^0.2.4", "@hugeicons/core-free-icons": "^3.1.1", "@hugeicons/react": "^1.1.4", - "@modelcontextprotocol/sdk": "^1.27.1", "@posthog/react": "^1.5.2", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.156.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bac9561..b3bbaad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,24 +14,15 @@ importers: '@better-auth/api-key': specifier: ^1.5.5 version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(better-auth@1.5.5(@tanstack/react-start@1.156.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@libsql/client@0.17.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.6.2)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0))) - '@better-auth/oauth-provider': - specifier: ^1.5.5 - version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@tanstack/react-start@1.156.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@libsql/client@0.17.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.6.2)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6)) '@flowershow/remark-wiki-link': specifier: ^3.3.1 version: 3.3.1 - '@hono/mcp': - specifier: ^0.2.4 - version: 0.2.4(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(hono-rate-limiter@0.4.2(hono@4.11.4))(hono@4.11.4)(zod@4.3.6) '@hugeicons/core-free-icons': specifier: ^3.1.1 version: 3.1.1 '@hugeicons/react': specifier: ^1.1.4 version: 1.1.4(react@19.2.3) - '@modelcontextprotocol/sdk': - specifier: ^1.27.1 - version: 1.27.1(zod@4.3.6) '@posthog/react': specifier: ^1.5.2 version: 1.5.2(@types/react@19.2.8)(posthog-js@1.335.2)(react@19.2.3) @@ -376,15 +367,6 @@ packages: '@better-auth/utils': ^0.3.0 mongodb: ^6.0.0 || ^7.0.0 - '@better-auth/oauth-provider@1.5.5': - resolution: {integrity: sha512-zH2uKtvd6406MysWCTBldPHTKCXEK8caMrNId03bh4ej4f2vU8+GfNGE+IyxARucHGI1T+Og7QrUgKAeA2jQUQ==} - peerDependencies: - '@better-auth/core': 1.5.5 - '@better-auth/utils': 0.3.1 - '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.5 - better-call: 1.3.2 - '@better-auth/prisma-adapter@1.5.5': resolution: {integrity: sha512-CliDd78CXHzzwQIXhCdwGr5Ml53i6JdCHWV7PYwTIJz9EAm6qb2RVBdpP3nqEfNjINGM22A6gfleCgCdZkTIZg==} peerDependencies: @@ -1226,20 +1208,6 @@ packages: '@flowershow/remark-wiki-link@3.3.1': resolution: {integrity: sha512-kIIbGXZigPU+X3pRn9tgrrnbVAwXi1a6gT9G4NcohJF0+WiReBPOaxa4d7G75+wfXdvw+8dkMUfn1m68qQuPKg==} - '@hono/mcp@0.2.4': - resolution: {integrity: sha512-BKH1qNTbrdqVeGHB32zIDNgTKdWUvlNoojM86oiKwwWtD+FQoNJbx2f3NnPvwua0+ujJed8IuEfgRKCNZ+2RNw==} - peerDependencies: - '@modelcontextprotocol/sdk': ^1.25.1 - hono: '*' - hono-rate-limiter: ^0.4.2 - zod: ^3.25.0 || ^4.0.0 - - '@hono/node-server@1.19.11': - resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: ^4 - '@hugeicons/core-free-icons@3.1.1': resolution: {integrity: sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg==} @@ -1566,16 +1534,6 @@ packages: cpu: [x64] os: [win32] - '@modelcontextprotocol/sdk@1.27.1': - resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - '@mongodb-js/saslprep@1.4.6': resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==} @@ -2260,10 +2218,6 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -2282,17 +2236,6 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2426,10 +2369,6 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} - engines: {node: '>=18'} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2452,22 +2391,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - caniuse-lite@1.0.30001766: resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} @@ -2549,28 +2476,12 @@ packages: engines: {node: '>=18'} hasBin: true - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -2578,10 +2489,6 @@ packages: core-js@3.47.0: resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - cross-fetch@4.1.0: resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} @@ -2645,10 +2552,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2783,23 +2686,12 @@ packages: sqlite3: optional: true - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.278: resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - encoding-sniffer@0.2.1: resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} @@ -2825,21 +2717,9 @@ packages: error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: @@ -2869,9 +2749,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -2884,18 +2761,6 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -2908,28 +2773,12 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - express-rate-limit@8.3.1: - resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} @@ -2956,10 +2805,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -2968,17 +2813,9 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - franc-min@6.2.0: resolution: {integrity: sha512-1uDIEUSlUZgvJa2AKYR/dmJC66v/PvGQ9mWfI9nOr/kPpMFyvswK0gPXOwpYJYiYD008PpHLkGfG58SPjQJFxw==} - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2987,9 +2824,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2998,14 +2832,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -3022,10 +2848,6 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3042,14 +2864,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - hast-util-from-dom@5.0.1: resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} @@ -3099,11 +2913,6 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - hono-rate-limiter@0.4.2: - resolution: {integrity: sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw==} - peerDependencies: - hono: ^4.1.1 - hono@4.11.4: resolution: {integrity: sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==} engines: {node: '>=16.9.0'} @@ -3121,10 +2930,6 @@ packages: htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -3145,10 +2950,6 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3158,14 +2959,6 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-arrayish@0.3.4: resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} @@ -3196,9 +2989,6 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - isbot@5.1.33: resolution: {integrity: sha512-P4Hgb5NqswjkI0J1CM6XKXon/sxKY1SuowE7Qx2hrBhIwICFyXy54mfgB5eMHXsbe/eStzzpbIGNOvGmz+dlKg==} engines: {node: '>=18'} @@ -3243,12 +3033,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3366,10 +3150,6 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -3415,17 +3195,9 @@ packages: mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -3516,14 +3288,6 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -3603,10 +3367,6 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - node-abi@3.87.0: resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} engines: {node: '>=10'} @@ -3639,21 +3399,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3669,10 +3417,6 @@ packages: parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3680,9 +3424,6 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3701,10 +3442,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -3736,10 +3473,6 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -3747,21 +3480,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} - engines: {node: '>=0.6'} - query-selector-shadow-dom@1.0.1: resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -3867,10 +3588,6 @@ packages: rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -3896,10 +3613,6 @@ packages: engines: {node: '>=10'} hasBin: true - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - seroval-plugins@1.5.0: resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==} engines: {node: '>=10'} @@ -3910,16 +3623,9 @@ packages: resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} engines: {node: '>=10'} - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - set-cookie-parser@3.0.1: resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3940,22 +3646,6 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3997,10 +3687,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -4103,10 +3789,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -4146,10 +3828,6 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4199,10 +3877,6 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -4221,10 +3895,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -4474,11 +4144,6 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod-to-json-schema@3.25.1: - resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} - peerDependencies: - zod: ^3.25 || ^4 - zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} @@ -4704,16 +4369,6 @@ snapshots: '@better-auth/utils': 0.3.1 mongodb: 7.1.0 - '@better-auth/oauth-provider@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@tanstack/react-start@1.156.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@libsql/client@0.17.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.6.2)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))': - dependencies: - '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) - '@better-auth/utils': 0.3.1 - '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.5(@tanstack/react-start@1.156.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@libsql/client@0.17.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.6.2)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0)) - better-call: 1.3.2(zod@4.3.6) - jose: 6.1.3 - zod: 4.3.6 - '@better-auth/prisma-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)': dependencies: '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) @@ -5203,18 +4858,6 @@ snapshots: mdast-util-to-markdown: 2.1.2 micromark-util-symbol: 2.0.1 - '@hono/mcp@0.2.4(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(hono-rate-limiter@0.4.2(hono@4.11.4))(hono@4.11.4)(zod@4.3.6)': - dependencies: - '@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6) - hono: 4.11.4 - hono-rate-limiter: 0.4.2(hono@4.11.4) - pkce-challenge: 5.0.1 - zod: 4.3.6 - - '@hono/node-server@1.19.11(hono@4.11.4)': - dependencies: - hono: 4.11.4 - '@hugeicons/core-free-icons@3.1.1': {} '@hugeicons/react@1.1.4(react@19.2.3)': @@ -5478,28 +5121,6 @@ snapshots: '@libsql/win32-x64-msvc@0.5.22': optional: true - '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': - dependencies: - '@hono/node-server': 1.19.11(hono@4.11.4) - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.11.4 - jose: 6.1.3 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) - transitivePeerDependencies: - - supports-color - '@mongodb-js/saslprep@1.4.6': dependencies: sparse-bitfield: 3.0.3 @@ -6168,11 +5789,6 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 - acorn-walk@8.3.2: {} acorn@8.14.0: {} @@ -6182,17 +5798,6 @@ snapshots: agent-base@7.1.4: optional: true - ajv-formats@3.0.1(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -6299,20 +5904,6 @@ snapshots: blake3-wasm@2.1.5: {} - body-parser@2.2.2: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - on-finished: 2.4.1 - qs: 6.15.0 - raw-body: 3.0.2 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - boolbase@1.0.0: {} braces@3.0.3: @@ -6337,20 +5928,8 @@ snapshots: ieee754: 1.2.1 optional: true - bytes@3.1.2: {} - cac@6.7.14: {} - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - caniuse-lite@1.0.30001766: {} ccount@2.0.1: {} @@ -6455,27 +6034,14 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 - content-disposition@1.0.1: {} - - content-type@1.0.5: {} - convert-source-map@2.0.0: {} cookie-es@2.0.0: {} - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - cookie@1.1.1: {} core-js@3.47.0: {} - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - cross-fetch@4.1.0: dependencies: node-fetch: 2.7.0 @@ -6545,8 +6111,6 @@ snapshots: defu@6.1.4: {} - depd@2.0.0: {} - dequal@2.0.3: {} detect-libc@2.0.2: {} @@ -6599,20 +6163,10 @@ snapshots: better-sqlite3: 12.6.2 kysely: 0.28.12 - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - electron-to-chromium@1.5.278: {} emoji-regex@8.0.0: {} - encodeurl@2.0.0: {} - encoding-sniffer@0.2.1: dependencies: iconv-lite: 0.6.3 @@ -6636,16 +6190,8 @@ snapshots: error-stack-parser-es@1.0.5: {} - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - esbuild-register@3.6.0(esbuild@0.25.4): dependencies: debug: 4.4.3 @@ -6766,8 +6312,6 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - escape-string-regexp@5.0.0: {} esprima@4.0.1: {} @@ -6776,14 +6320,6 @@ snapshots: dependencies: '@types/estree': 1.0.8 - etag@1.8.1: {} - - eventsource-parser@3.0.6: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.0.6 - exit-hook@2.2.1: {} expand-template@2.0.3: @@ -6791,52 +6327,10 @@ snapshots: expect-type@1.3.0: {} - express-rate-limit@8.3.1(express@5.2.1): - dependencies: - express: 5.2.1 - ip-address: 10.1.0 - - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.2 - content-disposition: 1.0.1 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.15.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - exsolve@1.0.8: {} extend@3.0.2: {} - fast-deep-equal@3.1.3: {} - - fast-uri@3.1.0: {} - fault@2.0.1: dependencies: format: 0.2.2 @@ -6859,61 +6353,26 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - format@0.2.2: {} formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 - forwarded@0.2.0: {} - franc-min@6.2.0: dependencies: trigram-utils: 2.0.1 - fresh@2.0.0: {} - fs-constants@1.0.0: optional: true fsevents@2.3.3: optional: true - function-bind@1.1.2: {} - gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -6929,8 +6388,6 @@ snapshots: glob-to-regexp@0.4.1: {} - gopd@1.2.0: {} - graceful-fs@4.2.11: {} h3@2.0.1-rc.11: @@ -6940,12 +6397,6 @@ snapshots: has-flag@4.0.0: {} - has-symbols@1.1.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - hast-util-from-dom@5.0.1: dependencies: '@types/hast': 3.0.4 @@ -7062,10 +6513,6 @@ snapshots: highlight.js@11.11.1: {} - hono-rate-limiter@0.4.2(hono@4.11.4): - dependencies: - hono: 4.11.4 - hono@4.11.4: {} html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1): @@ -7088,14 +6535,6 @@ snapshots: domutils: 3.2.2 entities: 7.0.1 - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -7122,22 +6561,15 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - ieee754@1.2.1: optional: true - inherits@2.0.4: {} + inherits@2.0.4: + optional: true ini@1.3.8: optional: true - ip-address@10.1.0: {} - - ipaddr.js@1.9.1: {} - is-arrayish@0.3.4: {} is-binary-path@2.1.0: @@ -7159,8 +6591,6 @@ snapshots: is-potential-custom-element-name@1.0.1: optional: true - is-promise@4.0.0: {} - isbot@5.1.33: {} isexe@2.0.0: {} @@ -7212,10 +6642,6 @@ snapshots: jsesc@3.1.0: {} - json-schema-traverse@1.0.0: {} - - json-schema-typed@8.0.2: {} - json5@2.2.3: {} katex@0.16.27: @@ -7315,8 +6741,6 @@ snapshots: markdown-table@3.0.4: {} - math-intrinsics@1.1.0: {} - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -7457,12 +6881,8 @@ snapshots: mdn-data@2.12.2: optional: true - media-typer@1.1.0: {} - memory-pager@1.5.0: {} - merge-descriptors@2.0.0: {} - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -7671,12 +7091,6 @@ snapshots: transitivePeerDependencies: - supports-color - mime-db@1.54.0: {} - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - mime@3.0.0: {} mimic-response@3.1.0: @@ -7742,8 +7156,6 @@ snapshots: napi-build-utils@2.0.0: optional: true - negotiator@1.0.0: {} - node-abi@3.87.0: dependencies: semver: 7.7.3 @@ -7769,19 +7181,12 @@ snapshots: dependencies: boolbase: 1.0.0 - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - ohash@2.0.11: {} - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - once@1.4.0: dependencies: wrappy: 1.0.2 + optional: true parse5-htmlparser2-tree-adapter@7.1.0: dependencies: @@ -7801,14 +7206,10 @@ snapshots: entities: 6.0.1 optional: true - parseurl@1.3.3: {} - path-key@3.1.1: {} path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} - pathe@2.0.3: {} pathval@2.0.1: {} @@ -7819,8 +7220,6 @@ snapshots: picomatch@4.0.3: {} - pkce-challenge@5.0.1: {} - postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -7882,11 +7281,6 @@ snapshots: '@types/node': 25.0.9 long: 5.3.2 - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -7895,21 +7289,8 @@ snapshots: punycode@2.3.1: {} - qs@6.15.0: - dependencies: - side-channel: 1.1.0 - query-selector-shadow-dom@1.0.1: {} - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - unpipe: 1.0.0 - rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -8057,7 +7438,8 @@ snapshots: require-directory@2.1.1: {} - require-from-string@2.0.2: {} + require-from-string@2.0.2: + optional: true reselect@5.1.1: {} @@ -8096,16 +7478,6 @@ snapshots: rou3@0.7.12: {} - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.3.0 - transitivePeerDependencies: - - supports-color - rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -8126,41 +7498,14 @@ snapshots: semver@7.7.3: {} - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - seroval-plugins@1.5.0(seroval@1.5.0): dependencies: seroval: 1.5.0 seroval@1.5.0: {} - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - set-cookie-parser@3.0.1: {} - setprototypeof@1.2.0: {} - sharp@0.33.5: dependencies: color: 4.2.3 @@ -8226,34 +7571,6 @@ snapshots: shell-quote@1.8.3: {} - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} simple-concat@1.0.1: @@ -8291,8 +7608,6 @@ snapshots: stackback@0.0.2: {} - statuses@2.0.2: {} - std-env@3.10.0: {} stoppable@1.1.0: {} @@ -8393,8 +7708,6 @@ snapshots: dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} - tough-cookie@6.0.0: dependencies: tldts: 7.0.19 @@ -8436,12 +7749,6 @@ snapshots: safe-buffer: 5.2.1 optional: true - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.2 - typescript@5.9.3: {} ufo@1.6.3: {} @@ -8507,8 +7814,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - unpipe@1.0.0: {} - unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -8529,8 +7834,6 @@ snapshots: util-deprecate@1.0.2: optional: true - vary@1.1.2: {} - vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -8739,7 +8042,8 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrappy@1.0.2: {} + wrappy@1.0.2: + optional: true ws@8.18.0: {} @@ -8787,10 +8091,6 @@ snapshots: cookie: 1.1.1 youch-core: 0.3.3 - zod-to-json-schema@3.25.1(zod@4.3.6): - dependencies: - zod: 4.3.6 - zod@3.22.3: {} zod@3.25.76: {} diff --git a/server/db/schema/auth-schema.ts b/server/db/schema/auth-schema.ts index df01398..74dc43f 100644 --- a/server/db/schema/auth-schema.ts +++ b/server/db/schema/auth-schema.ts @@ -135,168 +135,10 @@ export const apikey = sqliteTable( ], ); -export const oauthClient = sqliteTable( - "oauth_client", - { - id: text("id").primaryKey(), - clientId: text("client_id").notNull().unique(), - clientSecret: text("client_secret"), - disabled: integer("disabled", { mode: "boolean" }).default(false).notNull(), - skipConsent: integer("skip_consent", { mode: "boolean" }) - .default(false) - .notNull(), - enableEndSession: integer("enable_end_session", { mode: "boolean" }) - .default(false) - .notNull(), - subjectType: text("subject_type"), - scopes: text("scopes", { mode: "json" }).$type(), - userId: text("user_id").references(() => user.id, { onDelete: "cascade" }), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }), - createdAt: integer("created_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .notNull(), - updatedAt: integer("updated_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - name: text("name"), - uri: text("uri"), - icon: text("icon"), - contacts: text("contacts", { mode: "json" }).$type(), - tos: text("tos"), - policy: text("policy"), - softwareId: text("software_id"), - softwareVersion: text("software_version"), - softwareStatement: text("software_statement"), - redirectUris: text("redirect_uris", { mode: "json" }) - .$type() - .notNull(), - postLogoutRedirectUris: text("post_logout_redirect_uris", { - mode: "json", - }).$type(), - tokenEndpointAuthMethod: text("token_endpoint_auth_method"), - grantTypes: text("grant_types", { mode: "json" }).$type(), - responseTypes: text("response_types", { mode: "json" }).$type(), - public: integer("public", { mode: "boolean" }).default(false).notNull(), - type: text("type"), - requirePKCE: integer("require_pkce", { mode: "boolean" }), - referenceId: text("reference_id"), - metadata: text("metadata", { mode: "json" }).$type< - Record - >(), - }, - (table) => [ - index("oauth_client_clientId_idx").on(table.clientId), - index("oauth_client_userId_idx").on(table.userId), - index("oauth_client_referenceId_idx").on(table.referenceId), - ], -); - -export const oauthRefreshToken = sqliteTable( - "oauth_refresh_token", - { - id: text("id").primaryKey(), - token: text("token").notNull().unique(), - clientId: text("client_id") - .notNull() - .references(() => oauthClient.clientId, { onDelete: "cascade" }), - sessionId: text("session_id").references(() => session.id, { - onDelete: "set null", - }), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - referenceId: text("reference_id"), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), - createdAt: integer("created_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .notNull(), - revoked: integer("revoked", { mode: "timestamp_ms" }), - authTime: integer("auth_time", { mode: "timestamp_ms" }), - scopes: text("scopes", { mode: "json" }).$type().notNull(), - }, - (table) => [ - index("oauth_refresh_token_token_idx").on(table.token), - index("oauth_refresh_token_clientId_idx").on(table.clientId), - index("oauth_refresh_token_sessionId_idx").on(table.sessionId), - index("oauth_refresh_token_userId_idx").on(table.userId), - ], -); - -export const oauthAccessToken = sqliteTable( - "oauth_access_token", - { - id: text("id").primaryKey(), - token: text("token").notNull().unique(), - clientId: text("client_id") - .notNull() - .references(() => oauthClient.clientId, { onDelete: "cascade" }), - sessionId: text("session_id").references(() => session.id, { - onDelete: "set null", - }), - userId: text("user_id").references(() => user.id, { onDelete: "cascade" }), - referenceId: text("reference_id"), - refreshId: text("refresh_id").references(() => oauthRefreshToken.id, { - onDelete: "set null", - }), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), - createdAt: integer("created_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .notNull(), - scopes: text("scopes", { mode: "json" }).$type().notNull(), - }, - (table) => [ - index("oauth_access_token_token_idx").on(table.token), - index("oauth_access_token_clientId_idx").on(table.clientId), - index("oauth_access_token_sessionId_idx").on(table.sessionId), - index("oauth_access_token_userId_idx").on(table.userId), - index("oauth_access_token_refreshId_idx").on(table.refreshId), - ], -); - -export const oauthConsent = sqliteTable( - "oauth_consent", - { - id: text("id").primaryKey(), - clientId: text("client_id") - .notNull() - .references(() => oauthClient.clientId, { onDelete: "cascade" }), - userId: text("user_id").references(() => user.id, { onDelete: "cascade" }), - referenceId: text("reference_id"), - scopes: text("scopes", { mode: "json" }).$type().notNull(), - createdAt: integer("created_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .notNull(), - updatedAt: integer("updated_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - }, - (table) => [ - index("oauth_consent_clientId_idx").on(table.clientId), - index("oauth_consent_userId_idx").on(table.userId), - index("oauth_consent_referenceId_idx").on(table.referenceId), - ], -); - -export const jwks = sqliteTable("jwks", { - id: text("id").primaryKey(), - publicKey: text("public_key").notNull(), - privateKey: text("private_key").notNull(), - createdAt: integer("created_at", { mode: "timestamp_ms" }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .notNull(), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }), -}); - export const userRelations = relations(user, ({ many }) => ({ sessions: many(session), accounts: many(account), apiKeys: many(apikey), - oauthClients: many(oauthClient), - oauthRefreshTokens: many(oauthRefreshToken), - oauthAccessTokens: many(oauthAccessToken), - oauthConsents: many(oauthConsent), pages: many(page), })); @@ -320,65 +162,3 @@ export const apikeyRelations = relations(apikey, ({ one }) => ({ references: [user.id], }), })); - -export const oauthClientRelations = relations(oauthClient, ({ one, many }) => ({ - user: one(user, { - fields: [oauthClient.userId], - references: [user.id], - }), - accessTokens: many(oauthAccessToken), - refreshTokens: many(oauthRefreshToken), - consents: many(oauthConsent), -})); - -export const oauthRefreshTokenRelations = relations( - oauthRefreshToken, - ({ one, many }) => ({ - client: one(oauthClient, { - fields: [oauthRefreshToken.clientId], - references: [oauthClient.clientId], - }), - session: one(session, { - fields: [oauthRefreshToken.sessionId], - references: [session.id], - }), - user: one(user, { - fields: [oauthRefreshToken.userId], - references: [user.id], - }), - accessTokens: many(oauthAccessToken), - }), -); - -export const oauthAccessTokenRelations = relations( - oauthAccessToken, - ({ one }) => ({ - client: one(oauthClient, { - fields: [oauthAccessToken.clientId], - references: [oauthClient.clientId], - }), - session: one(session, { - fields: [oauthAccessToken.sessionId], - references: [session.id], - }), - user: one(user, { - fields: [oauthAccessToken.userId], - references: [user.id], - }), - refreshToken: one(oauthRefreshToken, { - fields: [oauthAccessToken.refreshId], - references: [oauthRefreshToken.id], - }), - }), -); - -export const oauthConsentRelations = relations(oauthConsent, ({ one }) => ({ - client: one(oauthClient, { - fields: [oauthConsent.clientId], - references: [oauthClient.clientId], - }), - user: one(user, { - fields: [oauthConsent.userId], - references: [user.id], - }), -})); diff --git a/server/index.ts b/server/index.ts index c61f97d..cd8682e 100644 --- a/server/index.ts +++ b/server/index.ts @@ -5,8 +5,6 @@ import { cleanerJob } from "./jobs/cleaner.job"; import { sendDiscordAlert } from "./lib/discord"; import { authRouter } from "./routes/auth.route"; import { dashboardRouter } from "./routes/dashboard.route"; -import { mcpRouter } from "./routes/mcp.route"; -import { oauthUiRouter } from "./routes/oauth-ui.route"; import { pageApiRouter } from "./routes/page-api.route"; import { viewRouter } from "./routes/view.route"; import { createContext } from "./trpc/context"; @@ -17,8 +15,6 @@ const app = new Hono<{ Bindings: Env }>(); app.route("/api/auth", authRouter); app.route("/api/v1/pages", pageApiRouter); -app.route("/", mcpRouter); -app.route("/", oauthUiRouter); app.all("/api/trpc/*", async (c) => { return await fetchRequestHandler({ endpoint: "/api/trpc", diff --git a/server/lib/auth.ts b/server/lib/auth.ts index 64bf583..ce36c83 100644 --- a/server/lib/auth.ts +++ b/server/lib/auth.ts @@ -1,28 +1,12 @@ import { env } from "cloudflare:workers"; import { apiKey } from "@better-auth/api-key"; -import { oauthProvider, type Scope } from "@better-auth/oauth-provider"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { jwt } from "better-auth/plugins"; import { eq } from "drizzle-orm"; import { db } from "../db/client"; import * as schema from "../db/schema"; import { findAvailableUsername } from "./username"; -const authOrigin = new URL(env.BETTER_AUTH_URL).origin; -const mcpResourceUrl = `${authOrigin}/mcp`; - -const oauthScopes = [ - "openid", - "profile", - "email", - "offline_access", - "mdto:pages:read", - "mdto:pages:write", - "mdto:user:read", - "mdto:user:write", -] as const satisfies Scope[]; - async function isUserNameTaken(name: string): Promise { const existing = await db .select({ id: schema.user.id }) @@ -84,23 +68,6 @@ export const auth = betterAuth({ enabled: false, }, }), - jwt({ - disableSettingJwtHeader: true, - jwt: { - issuer: `${authOrigin}/api/auth`, - }, - }), - oauthProvider({ - loginPage: `${authOrigin}/oauth/login`, - consentPage: `${authOrigin}/oauth/consent`, - scopes: [...oauthScopes], - validAudiences: [mcpResourceUrl], - grantTypes: ["authorization_code", "refresh_token"], - allowDynamicClientRegistration: true, - allowUnauthenticatedClientRegistration: true, - clientRegistrationDefaultScopes: [...oauthScopes], - clientRegistrationAllowedScopes: [...oauthScopes], - }), ], - trustedOrigins: [authOrigin], + trustedOrigins: [new URL(env.BETTER_AUTH_URL).origin], }); diff --git a/server/mcp/auth.ts b/server/mcp/auth.ts deleted file mode 100644 index 9f656d3..0000000 --- a/server/mcp/auth.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { env } from "cloudflare:workers"; -import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; -import { verifyJwsAccessToken } from "better-auth/oauth2"; -import { and, eq, gt } from "drizzle-orm"; -import type { Context } from "hono"; -import { db } from "../db/client"; -import * as schema from "../db/schema"; -import { auth } from "../lib/auth"; -import { createUserRepo } from "../repositories/user.repo"; -import { - MCP_PAGE_READ_SCOPE, - MCP_PAGE_WRITE_SCOPE, - MCP_SCOPES, - MCP_USER_READ_SCOPE, - MCP_USER_WRITE_SCOPE, -} from "./constants"; - -type McpAuthApp = { - Bindings: Env; - Variables: { - auth: AuthInfo; - }; -}; - -type McpPrincipal = { - user: { - id: string; - name: string; - }; - clientId: string; - sessionId: string; - scopes: string[]; -}; - -type McpAuthExtraUser = { - id: string; - name: string; -}; - -const authOrigin = new URL(env.BETTER_AUTH_URL).origin; -const authIssuer = `${authOrigin}/api/auth`; -const mcpResourceUrl = `${authOrigin}/mcp`; -const protectedResourcePath = "/.well-known/oauth-protected-resource/mcp"; - -function createUnauthorizedResponse(message: string) { - return new Response(message, { - status: 401, - headers: { - "WWW-Authenticate": `Bearer resource_metadata="${authOrigin}${protectedResourcePath}"`, - }, - }); -} - -export function getMcpResourceUrl() { - return mcpResourceUrl; -} - -export function getMcpAuthIssuer() { - return authIssuer; -} - -export function getProtectedResourceMetadata() { - return { - resource: mcpResourceUrl, - authorization_servers: [authIssuer], - jwks_uri: `${authIssuer}/jwks`, - scopes_supported: [...MCP_SCOPES], - bearer_methods_supported: ["header"], - }; -} - -export function hasRequiredScope( - authInfo: AuthInfo | undefined, - requiredScope: string, -) { - return authInfo?.scopes.includes(requiredScope) ?? false; -} - -function isMcpAuthExtraUser(value: unknown): value is McpAuthExtraUser { - if (!value || typeof value !== "object") { - return false; - } - - const candidate = value as Record; - return typeof candidate.id === "string" && typeof candidate.name === "string"; -} - -export function getPrincipalFromAuthInfo(authInfo: AuthInfo | undefined) { - const extra = authInfo?.extra; - if (!extra) { - throw new Error("Missing MCP auth context"); - } - - const user = extra.user; - if (!isMcpAuthExtraUser(user)) { - throw new Error("Missing authenticated user"); - } - - const sessionId = extra.sessionId; - if (typeof sessionId !== "string" || sessionId.length === 0) { - throw new Error("Missing authenticated session"); - } - - const clientId = extra.clientId; - if (typeof clientId !== "string" || clientId.length === 0) { - throw new Error("Missing OAuth client"); - } - - return { - user: { - id: user.id, - name: user.name, - }, - sessionId, - clientId, - scopes: authInfo?.scopes ?? [], - } satisfies McpPrincipal; -} - -export async function authorizeMcpRequest(c: Context) { - const authorization = c.req.header("authorization"); - const token = authorization?.startsWith("Bearer ") - ? authorization.slice("Bearer ".length) - : undefined; - - if (!token) { - return createUnauthorizedResponse("Missing bearer token"); - } - - try { - const jwtPayload = await verifyJwsAccessToken(token, { - jwksFetch: async () => auth.api.getJwks(), - verifyOptions: { - issuer: authIssuer, - audience: mcpResourceUrl, - }, - }); - - const userId = typeof jwtPayload.sub === "string" ? jwtPayload.sub : null; - const clientId = - typeof jwtPayload.client_id === "string" - ? jwtPayload.client_id - : typeof jwtPayload.azp === "string" - ? jwtPayload.azp - : null; - const sessionId = - typeof jwtPayload.sid === "string" ? jwtPayload.sid : null; - const scopeClaim = - typeof jwtPayload.scope === "string" ? jwtPayload.scope : ""; - - if (!userId || !clientId || !sessionId) { - return createUnauthorizedResponse("Invalid access token"); - } - - const [oauthClient] = await db - .select({ - clientId: schema.oauthClient.clientId, - disabled: schema.oauthClient.disabled, - }) - .from(schema.oauthClient) - .where(eq(schema.oauthClient.clientId, clientId)) - .limit(1) - .all(); - - if (!oauthClient || oauthClient.disabled) { - return createUnauthorizedResponse("Invalid access token"); - } - - const [activeSession] = await db - .select({ - id: schema.session.id, - userId: schema.session.userId, - }) - .from(schema.session) - .where( - and( - eq(schema.session.id, sessionId), - eq(schema.session.userId, userId), - gt(schema.session.expiresAt, new Date()), - ), - ) - .limit(1) - .all(); - - if (!activeSession) { - return createUnauthorizedResponse("Session expired"); - } - - const userRepo = createUserRepo(db); - const user = await userRepo.findById(userId); - if (!user) { - return createUnauthorizedResponse("User not found"); - } - - c.set("auth", { - token, - clientId, - scopes: scopeClaim.split(" ").filter(Boolean), - expiresAt: - typeof jwtPayload.exp === "number" ? jwtPayload.exp : undefined, - resource: new URL(mcpResourceUrl), - extra: { - clientId, - sessionId, - user: { - id: user.id, - name: user.name, - }, - }, - }); - - return null; - } catch { - return createUnauthorizedResponse("Invalid access token"); - } -} - -export { - type McpAuthApp, - type McpPrincipal, - MCP_PAGE_READ_SCOPE, - MCP_PAGE_WRITE_SCOPE, - MCP_USER_READ_SCOPE, - MCP_USER_WRITE_SCOPE, -}; diff --git a/server/mcp/constants.ts b/server/mcp/constants.ts deleted file mode 100644 index fa3979d..0000000 --- a/server/mcp/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const MCP_PAGE_READ_SCOPE = "mdto:pages:read"; -export const MCP_PAGE_WRITE_SCOPE = "mdto:pages:write"; -export const MCP_USER_READ_SCOPE = "mdto:user:read"; -export const MCP_USER_WRITE_SCOPE = "mdto:user:write"; - -export const MCP_SCOPES = [ - "openid", - "profile", - "email", - "offline_access", - MCP_PAGE_READ_SCOPE, - MCP_PAGE_WRITE_SCOPE, - MCP_USER_READ_SCOPE, - MCP_USER_WRITE_SCOPE, -] as const; - -export const MCP_PROTOCOL_VERSION = "2025-11-25"; diff --git a/server/mcp/server.ts b/server/mcp/server.ts deleted file mode 100644 index b1ed22e..0000000 --- a/server/mcp/server.ts +++ /dev/null @@ -1,432 +0,0 @@ -import { env } from "cloudflare:workers"; -import { StreamableHTTPTransport } from "@hono/mcp"; -import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; -import { - ErrorCode, - isInitializeRequest, - isJSONRPCRequest, - type JSONRPCError, - type JSONRPCMessage, - type JSONRPCRequest, - type JSONRPCResponse, - LATEST_PROTOCOL_VERSION, - type ServerNotification, - type ServerRequest, - SUPPORTED_PROTOCOL_VERSIONS, -} from "@modelcontextprotocol/sdk/types.js"; -import { TRPCError } from "@trpc/server"; -import { z } from "zod"; -import { db } from "../db/client"; -import { createManagedPageService } from "../services/managed-page.service"; -import { pageSlugSchema, themeSchema } from "../services/page-content.service"; -import { createUserService } from "../services/user.service"; -import { getViewCachePaths, purgePathsFromCache } from "../utils/cache"; -import { - getPrincipalFromAuthInfo, - hasRequiredScope, - MCP_PAGE_READ_SCOPE, - MCP_PAGE_WRITE_SCOPE, - MCP_USER_READ_SCOPE, - MCP_USER_WRITE_SCOPE, -} from "./auth"; - -type ToolExtra = RequestHandlerExtra; - -type JsonSchema = Record; -type JsonRpcResult = Record; - -type ToolDefinition = { - name: string; - description: string; - inputSchema: JsonSchema; - handler: (args: unknown, extra: ToolExtra) => Promise; -}; - -const publicBaseUrl = env.BETTER_AUTH_URL; -const serverInfo = { - name: "mdto", - version: "0.0.0", -} as const; - -function emptyObjectSchema(): JsonSchema { - return { - type: "object", - properties: {}, - additionalProperties: false, - }; -} - -function toJsonSchema(schema: z.ZodTypeAny | null): JsonSchema { - if (!schema) { - return emptyObjectSchema(); - } - - return z.toJSONSchema(schema); -} - -function jsonToolResult(data: unknown): JsonRpcResult { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(data, null, 2), - }, - ], - structuredContent: data, - }; -} - -function toolErrorResult(error: unknown) { - if (error instanceof TRPCError) { - return { - isError: true, - content: [ - { - type: "text" as const, - text: error.message, - }, - ], - structuredContent: { - code: error.code, - message: error.message, - }, - }; - } - - const message = - error instanceof Error ? error.message : "Internal server error"; - return { - isError: true, - content: [ - { - type: "text" as const, - text: message, - }, - ], - structuredContent: { - code: "INTERNAL_SERVER_ERROR", - message, - }, - }; -} - -function requirePrincipal(extra: ToolExtra) { - return getPrincipalFromAuthInfo(extra.authInfo); -} - -function requireScope(extra: ToolExtra, scope: string) { - if (!hasRequiredScope(extra.authInfo, scope)) { - throw new TRPCError({ - code: "FORBIDDEN", - message: `Missing required scope: ${scope}`, - }); - } -} - -function createToolHandler( - schema: TSchema, - handler: ( - args: TSchema extends z.ZodTypeAny ? z.infer : undefined, - extra: ToolExtra, - ) => Promise, -) { - return async (args: unknown, extra: ToolExtra) => { - try { - const parsedArgs = schema ? schema.parse(args ?? {}) : undefined; - return jsonToolResult( - await handler( - parsedArgs as TSchema extends z.ZodTypeAny - ? z.infer - : undefined, - extra, - ), - ); - } catch (error) { - return toolErrorResult(error); - } - }; -} - -const managedPageService = createManagedPageService({ env, db }); -const userService = createUserService({ db }); - -const createPageSchema = z.object({ - markdown: z.string(), - slug: pageSlugSchema.optional(), - theme: themeSchema.default("default"), - expiresAtMs: z.number().nullable().default(null), -}); - -const updatePageSchema = z.object({ - pageId: z.string().min(1), - markdown: z.string(), - newSlug: pageSlugSchema.optional(), - theme: themeSchema.optional(), - expiresAtMs: z.number().nullable().optional(), -}); - -const deletePageSchema = z.object({ - pageId: z.string().min(1), -}); - -const changeSlugSchema = z.object({ - pageId: z.string().min(1), - slug: pageSlugSchema, -}); - -const setDashboardVisibilitySchema = z.object({ - isDashboardPublic: z.boolean(), -}); - -const tools = [ - { - name: "list_pages", - description: "List managed pages for the authenticated mdto user.", - inputSchema: emptyObjectSchema(), - handler: createToolHandler(null, async (_args, extra) => { - requireScope(extra, MCP_PAGE_READ_SCOPE); - const { user } = requirePrincipal(extra); - return managedPageService.listForUser(user); - }), - }, - { - name: "create_page", - description: "Create a managed mdto page from markdown.", - inputSchema: toJsonSchema(createPageSchema), - handler: createToolHandler(createPageSchema, async (args, extra) => { - requireScope(extra, MCP_PAGE_WRITE_SCOPE); - const { user } = requirePrincipal(extra); - return managedPageService.createPage(args, user); - }), - }, - { - name: "update_page", - description: - "Update markdown, slug, theme, or expiration for a managed page.", - inputSchema: toJsonSchema(updatePageSchema), - handler: createToolHandler(updatePageSchema, async (args, extra) => { - requireScope(extra, MCP_PAGE_WRITE_SCOPE); - const { user } = requirePrincipal(extra); - const result = await managedPageService.updatePage(args, user); - await purgePathsFromCache(publicBaseUrl, [ - ...getViewCachePaths(`/${user.name}/${result.previousSlug}`), - ...getViewCachePaths(result.page.path), - ]); - return result.page; - }), - }, - { - name: "delete_page", - description: "Delete a managed mdto page.", - inputSchema: toJsonSchema(deletePageSchema), - handler: createToolHandler(deletePageSchema, async (args, extra) => { - requireScope(extra, MCP_PAGE_WRITE_SCOPE); - const { user } = requirePrincipal(extra); - const result = await managedPageService.deletePage(user.id, args.pageId); - await purgePathsFromCache(publicBaseUrl, [ - ...getViewCachePaths(`/${user.name}/${result.slug}`), - ]); - return result; - }), - }, - { - name: "change_slug", - description: "Change the slug of an existing managed mdto page.", - inputSchema: toJsonSchema(changeSlugSchema), - handler: createToolHandler(changeSlugSchema, async (args, extra) => { - requireScope(extra, MCP_PAGE_WRITE_SCOPE); - const { user } = requirePrincipal(extra); - const result = await managedPageService.changeSlug( - user, - args.pageId, - args.slug, - ); - await purgePathsFromCache(publicBaseUrl, [ - ...getViewCachePaths(`/${user.name}/${result.previousSlug}`), - ...getViewCachePaths(result.path), - ]); - return result; - }), - }, - { - name: "get_dashboard_visibility", - description: "Read whether the authenticated user's dashboard is public.", - inputSchema: emptyObjectSchema(), - handler: createToolHandler(null, async (_args, extra) => { - requireScope(extra, MCP_USER_READ_SCOPE); - const { user } = requirePrincipal(extra); - return userService.getDashboardVisibility(user.id); - }), - }, - { - name: "set_dashboard_visibility", - description: "Set whether the authenticated user's dashboard is public.", - inputSchema: toJsonSchema(setDashboardVisibilitySchema), - handler: createToolHandler( - setDashboardVisibilitySchema, - async (args, extra) => { - requireScope(extra, MCP_USER_WRITE_SCOPE); - const { user } = requirePrincipal(extra); - return userService.setDashboardVisibility( - user.id, - args.isDashboardPublic, - ); - }, - ), - }, -] satisfies ToolDefinition[]; - -const toolsByName = new Map(tools.map((tool) => [tool.name, tool])); - -function jsonRpcError( - id: JSONRPCRequest["id"] | undefined, - code: number, - message: string, - data?: unknown, -) { - return { - jsonrpc: "2.0", - id, - error: { - code, - message, - ...(data === undefined ? {} : { data }), - }, - } satisfies JSONRPCError; -} - -function jsonRpcResult(id: JSONRPCRequest["id"], result: JsonRpcResult) { - return { - jsonrpc: "2.0", - id, - result, - } satisfies JSONRPCResponse; -} - -function negotiateProtocolVersion(requestedVersion: unknown) { - return typeof requestedVersion === "string" && - SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) - ? requestedVersion - : LATEST_PROTOCOL_VERSION; -} - -async function handleInitialize( - message: JSONRPCRequest, -): Promise { - return jsonRpcResult(message.id, { - protocolVersion: negotiateProtocolVersion( - typeof message.params === "object" && message.params - ? message.params.protocolVersion - : undefined, - ), - capabilities: { - tools: { - listChanged: false, - }, - }, - serverInfo, - }); -} - -async function handlePing( - message: JSONRPCRequest, -): Promise { - return jsonRpcResult(message.id, {}); -} - -async function handleToolsList( - message: JSONRPCRequest, -): Promise { - return jsonRpcResult(message.id, { - tools: tools.map((tool) => ({ - name: tool.name, - description: tool.description, - inputSchema: tool.inputSchema, - })), - }); -} - -async function handleToolsCall( - message: JSONRPCRequest, - extra: ToolExtra, -): Promise { - const params = message.params; - if ( - !params || - typeof params !== "object" || - typeof params.name !== "string" - ) { - return jsonRpcError( - message.id, - ErrorCode.InvalidParams, - "Invalid tool call", - ); - } - - const tool = toolsByName.get(params.name); - if (!tool) { - return jsonRpcError( - message.id, - ErrorCode.InvalidParams, - `Tool ${params.name} not found`, - ); - } - - const result = await tool.handler( - "arguments" in params ? params.arguments : undefined, - extra, - ); - return jsonRpcResult(message.id, result); -} - -async function dispatchMessage( - message: JSONRPCMessage, - extra: ToolExtra, -): Promise { - if (!isJSONRPCRequest(message)) { - return null; - } - - if (isInitializeRequest(message)) { - return handleInitialize(message); - } - - switch (message.method) { - case "ping": - return handlePing(message); - case "tools/list": - return handleToolsList(message); - case "tools/call": - return handleToolsCall(message, extra); - case "notifications/initialized": - return null; - default: - return jsonRpcError( - message.id, - ErrorCode.MethodNotFound, - `Method not found: ${message.method}`, - ); - } -} - -export function createJsonResponseMcpTransport() { - const transport = new StreamableHTTPTransport({ - // mdto currently exposes MCP as a stateless POST -> JSON endpoint. - // We intentionally avoid session-backed streaming semantics here. - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - - transport.onmessage = async (message, extra) => { - const result = await dispatchMessage(message, extra as ToolExtra); - if (!result || !isJSONRPCRequest(message)) { - return; - } - - await transport.send(result, { - relatedRequestId: message.id, - }); - }; - - return transport; -} diff --git a/server/routes/auth.route.ts b/server/routes/auth.route.ts index acf67e8..436d987 100644 --- a/server/routes/auth.route.ts +++ b/server/routes/auth.route.ts @@ -1,4 +1,4 @@ -import { type Context, Hono } from "hono"; +import { Hono } from "hono"; import { auth } from "../lib/auth"; type AuthApp = { @@ -7,64 +7,6 @@ type AuthApp = { export const authRouter = new Hono(); -const PUBLIC_AUTH_ALLOWED_METHODS = "GET, POST, OPTIONS"; -const PUBLIC_AUTH_ALLOWED_HEADERS = [ - "accept", - "authorization", - "content-type", - "mcp-protocol-version", -].join(", "); - -function isPublicOAuthPath(pathname: string) { - return ( - pathname === "/api/auth/jwks" || pathname.startsWith("/api/auth/oauth2/") - ); -} - -function setPublicOAuthCorsHeaders(c: Context) { - c.header("Access-Control-Allow-Origin", "*"); - c.header("Access-Control-Allow-Methods", PUBLIC_AUTH_ALLOWED_METHODS); - c.header("Access-Control-Allow-Headers", PUBLIC_AUTH_ALLOWED_HEADERS); - c.header("Access-Control-Max-Age", "86400"); -} - -function appendPublicOAuthCorsHeaders(response: Response) { - response.headers.set("Access-Control-Allow-Origin", "*"); - response.headers.set( - "Access-Control-Allow-Methods", - PUBLIC_AUTH_ALLOWED_METHODS, - ); - response.headers.set( - "Access-Control-Allow-Headers", - PUBLIC_AUTH_ALLOWED_HEADERS, - ); - response.headers.set("Access-Control-Max-Age", "86400"); - return response; -} - -authRouter.use("*", async (c, next) => { - const pathname = new URL(c.req.url).pathname; - if (!isPublicOAuthPath(pathname)) { - await next(); - return; - } - - setPublicOAuthCorsHeaders(c); - if (c.req.method === "OPTIONS") { - return c.body(null, 204); - } - - await next(); -}); - authRouter.on(["POST", "GET"], "*", (c) => { - const pathname = new URL(c.req.url).pathname; - const response = auth.handler(c.req.raw); - if (!isPublicOAuthPath(pathname)) { - return response; - } - - return Promise.resolve(response).then((resolvedResponse) => { - return appendPublicOAuthCorsHeaders(resolvedResponse); - }); + return auth.handler(c.req.raw); }); diff --git a/server/routes/mcp.route.ts b/server/routes/mcp.route.ts deleted file mode 100644 index 64cea99..0000000 --- a/server/routes/mcp.route.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - oauthProviderAuthServerMetadata, - oauthProviderOpenIdConfigMetadata, -} from "@better-auth/oauth-provider"; -import { type Context, Hono } from "hono"; -import { auth } from "../lib/auth"; -import { - authorizeMcpRequest, - getProtectedResourceMetadata, - type McpAuthApp, -} from "../mcp/auth"; -import { MCP_PROTOCOL_VERSION } from "../mcp/constants"; -import { createJsonResponseMcpTransport } from "../mcp/server"; - -export const mcpRouter = new Hono(); -const MCP_ALLOWED_METHODS = "POST, OPTIONS"; -const MCP_ALLOWED_HEADERS = [ - "authorization", - "content-type", - "accept", - "mcp-protocol-version", -].join(", "); -const MCP_EXPOSED_HEADERS = "mcp-protocol-version"; -const PUBLIC_METADATA_ALLOWED_METHODS = "GET, OPTIONS"; -const PUBLIC_METADATA_ALLOWED_HEADERS = ["accept", "mcp-protocol-version"].join( - ", ", -); - -function setMcpCorsHeaders(c: Context, origin?: string) { - if (!origin) { - return; - } - - c.header("Access-Control-Allow-Origin", origin); - c.header("Access-Control-Allow-Methods", MCP_ALLOWED_METHODS); - c.header("Access-Control-Allow-Headers", MCP_ALLOWED_HEADERS); - c.header("Access-Control-Expose-Headers", MCP_EXPOSED_HEADERS); - c.header("Access-Control-Max-Age", "86400"); - c.header("Vary", "Origin"); -} - -function appendMcpCorsHeaders(response: Response, origin?: string) { - if (!origin) { - return response; - } - - response.headers.set("Access-Control-Allow-Origin", origin); - response.headers.set("Access-Control-Allow-Methods", MCP_ALLOWED_METHODS); - response.headers.set("Access-Control-Allow-Headers", MCP_ALLOWED_HEADERS); - response.headers.set("Access-Control-Expose-Headers", MCP_EXPOSED_HEADERS); - response.headers.set("Access-Control-Max-Age", "86400"); - response.headers.set("Vary", "Origin"); - return response; -} - -function setPublicMetadataCorsHeaders(c: Context) { - c.header("Access-Control-Allow-Origin", "*"); - c.header("Access-Control-Allow-Methods", PUBLIC_METADATA_ALLOWED_METHODS); - c.header("Access-Control-Allow-Headers", PUBLIC_METADATA_ALLOWED_HEADERS); - c.header("Access-Control-Max-Age", "86400"); -} - -function appendPublicMetadataCorsHeaders(response: Response) { - response.headers.set("Access-Control-Allow-Origin", "*"); - response.headers.set( - "Access-Control-Allow-Methods", - PUBLIC_METADATA_ALLOWED_METHODS, - ); - response.headers.set( - "Access-Control-Allow-Headers", - PUBLIC_METADATA_ALLOWED_HEADERS, - ); - response.headers.set("Access-Control-Max-Age", "86400"); - return response; -} - -mcpRouter.use("/.well-known/*", async (c, next) => { - setPublicMetadataCorsHeaders(c); - - if (c.req.method === "OPTIONS") { - return c.body(null, 204); - } - - await next(); -}); - -async function serveAuthServerMetadata(c: Context) { - const handler = oauthProviderAuthServerMetadata(auth); - return appendPublicMetadataCorsHeaders(await handler(c.req.raw)); -} - -async function serveOpenIdConfigMetadata(c: Context) { - const handler = oauthProviderOpenIdConfigMetadata(auth); - return appendPublicMetadataCorsHeaders(await handler(c.req.raw)); -} - -mcpRouter.get( - "/.well-known/oauth-authorization-server", - serveAuthServerMetadata, -); -mcpRouter.get( - "/.well-known/oauth-authorization-server/api/auth", - serveAuthServerMetadata, -); - -mcpRouter.get("/.well-known/openid-configuration", serveOpenIdConfigMetadata); -mcpRouter.get( - "/.well-known/openid-configuration/api/auth", - serveOpenIdConfigMetadata, -); - -mcpRouter.get("/.well-known/oauth-protected-resource", (c) => { - return c.json(getProtectedResourceMetadata()); -}); - -mcpRouter.get("/.well-known/oauth-protected-resource/mcp", (c) => { - return c.json(getProtectedResourceMetadata()); -}); - -mcpRouter.use("/mcp", async (c, next) => { - const origin = c.req.header("origin"); - setMcpCorsHeaders(c, origin); - - if (c.req.method === "OPTIONS") { - return c.body(null, 204); - } - - if (c.req.method !== "POST") { - return c.json( - { - jsonrpc: "2.0", - error: { - code: -32000, - message: - "Method not allowed. mdto MCP only supports POST JSON requests.", - }, - id: null, - }, - { - status: 405, - headers: { - Allow: MCP_ALLOWED_METHODS, - }, - }, - ); - } - - const authResponse = await authorizeMcpRequest(c); - if (authResponse) { - return appendMcpCorsHeaders(authResponse, origin); - } - - await next(); -}); - -mcpRouter.post("/mcp", async (c) => { - c.header("mcp-protocol-version", MCP_PROTOCOL_VERSION); - return createJsonResponseMcpTransport().handleRequest(c); -}); diff --git a/server/routes/oauth-ui.route.ts b/server/routes/oauth-ui.route.ts deleted file mode 100644 index 18dced9..0000000 --- a/server/routes/oauth-ui.route.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Hono } from "hono"; - -export const oauthUiRouter = new Hono<{ Bindings: Env }>(); - -oauthUiRouter.get("/oauth/login", async (c) => { - return c.env.ASSETS.fetch("http://localhost/oauth/login/index.html"); -}); - -oauthUiRouter.get("/oauth/consent", async (c) => { - return c.env.ASSETS.fetch("http://localhost/oauth/consent/index.html"); -}); diff --git a/test/auth-schema.oauth-json.test.ts b/test/auth-schema.oauth-json.test.ts deleted file mode 100644 index 8514923..0000000 --- a/test/auth-schema.oauth-json.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import "./helpers/mock-iso-language"; -import { eq } from "drizzle-orm"; -import { describe, expect, it } from "vitest"; -import * as schema from "../server/db/schema"; -import { seedUser, testUser } from "./helpers/seed"; -import { registerWorkersTestHooks, testDb } from "./helpers/workers"; - -registerWorkersTestHooks({ seedBaseUsers: false }); - -describe("oauth auth schema json columns", () => { - it("round-trips oauth arrays and metadata through drizzle", async () => { - await seedUser(testDb); - - await testDb.insert(schema.oauthClient).values({ - id: "client_1", - clientId: "client-id-1", - clientSecret: "secret", - userId: testUser.id, - scopes: ["openid", "profile", "mdto:pages:read"], - contacts: ["ops@example.com"], - redirectUris: ["https://example.com/callback"], - postLogoutRedirectUris: ["https://example.com/logout"], - grantTypes: ["authorization_code", "refresh_token"], - responseTypes: ["code"], - metadata: { - label: "example-client", - }, - }); - - await testDb.insert(schema.oauthRefreshToken).values({ - id: "refresh_1", - token: "refresh-token-1", - clientId: "client-id-1", - userId: testUser.id, - expiresAt: new Date("2026-12-31T00:00:00.000Z"), - scopes: ["offline_access", "mdto:pages:read"], - }); - - await testDb.insert(schema.oauthAccessToken).values({ - id: "access_1", - token: "access-token-1", - clientId: "client-id-1", - userId: testUser.id, - refreshId: "refresh_1", - expiresAt: new Date("2026-12-31T00:00:00.000Z"), - scopes: ["mdto:pages:read"], - }); - - await testDb.insert(schema.oauthConsent).values({ - id: "consent_1", - clientId: "client-id-1", - userId: testUser.id, - scopes: ["openid", "profile"], - }); - - const [oauthClient] = await testDb - .select() - .from(schema.oauthClient) - .where(eq(schema.oauthClient.clientId, "client-id-1")); - const [refreshToken] = await testDb - .select() - .from(schema.oauthRefreshToken) - .where(eq(schema.oauthRefreshToken.id, "refresh_1")); - const [accessToken] = await testDb - .select() - .from(schema.oauthAccessToken) - .where(eq(schema.oauthAccessToken.id, "access_1")); - const [consent] = await testDb - .select() - .from(schema.oauthConsent) - .where(eq(schema.oauthConsent.id, "consent_1")); - - expect(oauthClient?.scopes).toEqual([ - "openid", - "profile", - "mdto:pages:read", - ]); - expect(oauthClient?.contacts).toEqual(["ops@example.com"]); - expect(oauthClient?.redirectUris).toEqual(["https://example.com/callback"]); - expect(oauthClient?.postLogoutRedirectUris).toEqual([ - "https://example.com/logout", - ]); - expect(oauthClient?.grantTypes).toEqual([ - "authorization_code", - "refresh_token", - ]); - expect(oauthClient?.responseTypes).toEqual(["code"]); - expect(oauthClient?.metadata).toEqual({ - label: "example-client", - }); - expect(refreshToken?.scopes).toEqual(["offline_access", "mdto:pages:read"]); - expect(accessToken?.scopes).toEqual(["mdto:pages:read"]); - expect(consent?.scopes).toEqual(["openid", "profile"]); - }); -}); diff --git a/test/helpers/mcp.ts b/test/helpers/mcp.ts deleted file mode 100644 index f67b183..0000000 --- a/test/helpers/mcp.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { auth } from "../../server/lib/auth"; -import { getMcpAuthIssuer, getMcpResourceUrl } from "../../server/mcp/auth"; -import { MCP_PROTOCOL_VERSION } from "../../server/mcp/constants"; -import { type WorkerFetchResult, workerFetch } from "./worker-http"; - -export type JsonRpcSuccess = { - jsonrpc: "2.0"; - id: string | number | null; - result: T; -}; - -export type JsonRpcError = { - jsonrpc: "2.0"; - id: string | number | null; - error: { - code: number; - message: string; - data?: unknown; - }; -}; - -type JsonRpcBody = JsonRpcSuccess | JsonRpcError; - -export async function mcpFetch( - body: Record, - input?: { - token?: string; - protocolVersion?: string; - origin?: string; - }, -): Promise | string>> { - const headers = new Headers({ - accept: "application/json, text/event-stream", - "content-type": "application/json", - }); - - if (input?.protocolVersion) { - headers.set("mcp-protocol-version", input.protocolVersion); - } - - if (input?.token) { - headers.set("authorization", `Bearer ${input.token}`); - } - if (input?.origin) { - headers.set("origin", input.origin); - } - - return workerFetch | string>("http://localhost/mcp", { - method: "POST", - headers, - body: JSON.stringify(body), - }); -} - -export async function initializeMcp( - token: string, - input?: { origin?: string }, -) { - const response = await mcpFetch<{ - protocolVersion: string; - serverInfo: { - name: string; - version: string; - }; - }>( - { - jsonrpc: "2.0", - id: "init-1", - method: "initialize", - params: { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: {}, - clientInfo: { - name: "vitest", - version: "1.0.0", - }, - }, - }, - { token, origin: input?.origin }, - ); - - if (!("result" in response.body)) { - throw new Error("Failed to initialize MCP"); - } - - return response.body.result.protocolVersion; -} - -export async function issueMcpAccessToken(input: { - userId: string; - sessionId: string; - scopes?: string[]; - clientId?: string; - expiresInSeconds?: number; -}) { - const scopes = input.scopes ?? [ - "mdto:pages:read", - "mdto:pages:write", - "mdto:user:read", - "mdto:user:write", - ]; - - const result = await auth.api.signJWT({ - body: { - payload: { - sub: input.userId, - sid: input.sessionId, - azp: input.clientId ?? "mcp-test-client", - aud: getMcpResourceUrl(), - scope: scopes.join(" "), - }, - overrideOptions: { - jwt: { - issuer: getMcpAuthIssuer(), - expirationTime: input.expiresInSeconds - ? `${input.expiresInSeconds} seconds` - : "1 hour", - }, - }, - }, - }); - - return result.token; -} diff --git a/test/helpers/seed.ts b/test/helpers/seed.ts index e66ffe3..668e53c 100644 --- a/test/helpers/seed.ts +++ b/test/helpers/seed.ts @@ -24,14 +24,9 @@ export function createTestDb(db: D1Database) { export async function resetData(db: ReturnType) { await db.delete(schema.page); - await db.delete(schema.oauthAccessToken); - await db.delete(schema.oauthRefreshToken); - await db.delete(schema.oauthConsent); - await db.delete(schema.oauthClient); await db.delete(schema.apikey); await db.delete(schema.account); await db.delete(schema.session); - await db.delete(schema.jwks); await db.delete(schema.verification); await db.delete(schema.user); } @@ -196,28 +191,3 @@ export async function seedSessionForUser( expiresAt, }; } - -export async function seedOauthClient( - db: ReturnType, - input: { - clientId: string; - userId: string; - disabled?: boolean; - redirectUris?: string[]; - }, -) { - const now = new Date(); - await db.insert(schema.oauthClient).values({ - id: `oauth_client_${input.clientId}`, - clientId: input.clientId, - userId: input.userId, - disabled: input.disabled ?? false, - redirectUris: input.redirectUris ?? ["https://example.com/callback"], - createdAt: now, - updatedAt: now, - }); - - return { - clientId: input.clientId, - }; -} diff --git a/test/mcp.remote.test.ts b/test/mcp.remote.test.ts deleted file mode 100644 index 883c570..0000000 --- a/test/mcp.remote.test.ts +++ /dev/null @@ -1,700 +0,0 @@ -import "./helpers/mock-iso-language"; -import { eq } from "drizzle-orm"; -import { describe, expect, it } from "vitest"; -import * as schema from "../server/db/schema"; -import { MCP_PROTOCOL_VERSION } from "../server/mcp/constants"; -import { initializeMcp, issueMcpAccessToken, mcpFetch } from "./helpers/mcp"; -import { buildPageMarkdown, getPageById } from "./helpers/page-api-fixtures"; -import { - seedOauthClient, - seedPageForUser, - seedSessionForUser, - testUser, -} from "./helpers/seed"; -import { workerFetch } from "./helpers/worker-http"; -import { otherUser, registerWorkersTestHooks, testDb } from "./helpers/workers"; - -registerWorkersTestHooks(); - -async function issueTokenForUser( - user: { - id: string; - name: string; - email: string; - }, - scopes?: string[], -) { - const clientId = `mcp-client-${crypto.randomUUID()}`; - const session = await seedSessionForUser(testDb, user, { - id: `session_${user.id}_${crypto.randomUUID()}`, - }); - await seedOauthClient(testDb, { - clientId, - userId: user.id, - }); - - const token = await issueMcpAccessToken({ - userId: user.id, - sessionId: session.id, - scopes, - clientId, - }); - - return { - token, - sessionId: session.id, - clientId, - }; -} - -describe("remote MCP", () => { - it("serves OAuth discovery and public auth endpoints with browser-readable CORS", async () => { - const authMetadataResponse = await workerFetch<{ - issuer: string; - }>("http://localhost/.well-known/oauth-authorization-server/api/auth", { - headers: { - origin: "https://client.example", - }, - }); - - expect(authMetadataResponse.status).toBe(200); - expect(authMetadataResponse.body.issuer).toContain("/api/auth"); - expect( - authMetadataResponse.response.headers.get("access-control-allow-origin"), - ).toBe("*"); - - const jwksResponse = await workerFetch<{ - keys: unknown[]; - }>("http://localhost/api/auth/jwks", { - headers: { - origin: "https://client.example", - }, - }); - - expect(jwksResponse.status).toBe(200); - expect(Array.isArray(jwksResponse.body.keys)).toBe(true); - expect( - jwksResponse.response.headers.get("access-control-allow-origin"), - ).toBe("*"); - - const registerPreflightResponse = await workerFetch( - "http://localhost/api/auth/oauth2/register", - { - method: "OPTIONS", - headers: { - origin: "https://client.example", - "access-control-request-method": "POST", - "access-control-request-headers": "content-type", - }, - }, - ); - - expect(registerPreflightResponse.status).toBe(204); - expect( - registerPreflightResponse.response.headers.get( - "access-control-allow-origin", - ), - ).toBe("*"); - expect( - registerPreflightResponse.response.headers.get( - "access-control-allow-methods", - ), - ).toBe("GET, POST, OPTIONS"); - }); - - it("handles browser preflight requests for cross-origin MCP clients", async () => { - const { response, status, body } = await workerFetch( - "http://localhost/mcp", - { - method: "OPTIONS", - headers: { - origin: "https://client.example", - "access-control-request-method": "POST", - "access-control-request-headers": - "authorization, content-type, mcp-protocol-version", - }, - }, - ); - - expect(status).toBe(204); - expect(body).toBeNull(); - expect(response.headers.get("access-control-allow-origin")).toBe( - "https://client.example", - ); - expect(response.headers.get("access-control-allow-methods")).toBe( - "POST, OPTIONS", - ); - expect(response.headers.get("access-control-allow-headers")).toContain( - "authorization", - ); - expect(response.headers.get("access-control-allow-headers")).not.toContain( - "mcp-session-id", - ); - expect(response.headers.get("access-control-allow-headers")).not.toContain( - "last-event-id", - ); - expect(response.headers.get("access-control-expose-headers")).toBe( - "mcp-protocol-version", - ); - }); - - it("rejects GET to keep the MCP endpoint POST-only", async () => { - const { token } = await issueTokenForUser(testUser); - const { response, status, body } = await workerFetch<{ - jsonrpc: string; - error: { - code: number; - message: string; - }; - id: null; - }>("http://localhost/mcp", { - method: "GET", - headers: { - authorization: `Bearer ${token}`, - accept: "application/json", - }, - }); - - expect(status).toBe(405); - expect(response.headers.get("allow")).toBe("POST, OPTIONS"); - expect(body.error.message).toBe( - "Method not allowed. mdto MCP only supports POST JSON requests.", - ); - }); - - it("rejects initialize without a bearer token", async () => { - const { response, status, body } = await mcpFetch( - { - jsonrpc: "2.0", - id: "init-missing-token", - method: "initialize", - params: { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: {}, - clientInfo: { - name: "vitest", - version: "1.0.0", - }, - }, - }, - {}, - ); - - expect(status).toBe(401); - expect(body).toBe("Missing bearer token"); - expect(response.headers.get("www-authenticate")).toContain( - "/.well-known/oauth-protected-resource/mcp", - ); - }); - - it("allows initialize from a different browser origin after OAuth", async () => { - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token, { - origin: "https://client.example", - }); - - expect(protocolVersion).toBe(MCP_PROTOCOL_VERSION); - }); - - it("negotiates older supported protocol versions during initialize", async () => { - const { token } = await issueTokenForUser(testUser); - const { status, body } = await mcpFetch<{ - protocolVersion: string; - }>( - { - jsonrpc: "2.0", - id: "init-older-version", - method: "initialize", - params: { - protocolVersion: "2025-06-18", - capabilities: {}, - clientInfo: { - name: "vitest", - version: "1.0.0", - }, - }, - }, - { token }, - ); - - expect(status).toBe(200); - if (!("result" in body)) { - throw new Error("Expected initialize result"); - } - - expect(body.result.protocolVersion).toBe("2025-06-18"); - }); - - it("rejects initialize with an invalid token", async () => { - const { status, body } = await mcpFetch( - { - jsonrpc: "2.0", - id: "init-invalid-token", - method: "initialize", - params: { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: {}, - clientInfo: { - name: "vitest", - version: "1.0.0", - }, - }, - }, - { token: "bad-token" }, - ); - - expect(status).toBe(401); - expect(body).toBe("Invalid access token"); - }); - - it("rejects initialize when the OAuth client is disabled", async () => { - const clientId = `mcp-client-${crypto.randomUUID()}`; - const session = await seedSessionForUser(testDb, testUser, { - id: `session_${testUser.id}_${crypto.randomUUID()}`, - }); - await seedOauthClient(testDb, { - clientId, - userId: testUser.id, - disabled: true, - }); - - const token = await issueMcpAccessToken({ - userId: testUser.id, - sessionId: session.id, - clientId, - }); - - const { status, body } = await mcpFetch( - { - jsonrpc: "2.0", - id: "init-disabled-client", - method: "initialize", - params: { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: {}, - clientInfo: { - name: "vitest", - version: "1.0.0", - }, - }, - }, - { token }, - ); - - expect(status).toBe(401); - expect(body).toBe("Invalid access token"); - }); - - it("lists the v1 tools after initialize", async () => { - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token); - - const { status, body } = await mcpFetch<{ - tools: Array<{ name: string }>; - }>( - { - jsonrpc: "2.0", - id: "tools-list", - method: "tools/list", - params: {}, - }, - { - token, - protocolVersion, - }, - ); - - expect(status).toBe(200); - if (!("result" in body)) { - throw new Error("Expected tools/list result"); - } - - expect(body.result.tools.map((tool) => tool.name)).toEqual([ - "list_pages", - "create_page", - "update_page", - "delete_page", - "change_slug", - "get_dashboard_visibility", - "set_dashboard_visibility", - ]); - }); - - it("supports create, list, update, change slug, and delete for the same user", async () => { - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token); - const markdown = buildPageMarkdown({ - title: "MCP Title", - description: "Created from MCP", - }); - - const createResponse = await mcpFetch<{ - content: Array<{ text: string }>; - structuredContent: { - id: string; - slug: string; - path: string; - title: string; - }; - }>( - { - jsonrpc: "2.0", - id: "create-page", - method: "tools/call", - params: { - name: "create_page", - arguments: { - markdown, - slug: "mcp-page", - theme: "default", - expiresAtMs: null, - }, - }, - }, - { token, protocolVersion }, - ); - - expect(createResponse.status).toBe(200); - if (!("result" in createResponse.body)) { - throw new Error("Expected create_page result"); - } - - expect(createResponse.body.result.isError).toBeUndefined(); - const createdPage = createResponse.body.result.structuredContent; - expect(createdPage.slug).toBe("mcp-page"); - - const listResponse = await mcpFetch<{ - structuredContent: Array<{ id: string; slug: string }>; - }>( - { - jsonrpc: "2.0", - id: "list-pages", - method: "tools/call", - params: { - name: "list_pages", - arguments: {}, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in listResponse.body)) { - throw new Error("Expected list_pages result"); - } - - expect(listResponse.body.result.structuredContent).toEqual([ - expect.objectContaining({ - id: createdPage.id, - slug: "mcp-page", - }), - ]); - - const updateResponse = await mcpFetch<{ - structuredContent: { - id: string; - slug: string; - title: string; - }; - }>( - { - jsonrpc: "2.0", - id: "update-page", - method: "tools/call", - params: { - name: "update_page", - arguments: { - pageId: createdPage.id, - markdown: buildPageMarkdown({ - title: "Updated Title", - description: "Updated from MCP", - }), - newSlug: "mcp-page-updated", - theme: "matrix", - }, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in updateResponse.body)) { - throw new Error("Expected update_page result"); - } - - expect(updateResponse.body.result.structuredContent.slug).toBe( - "mcp-page-updated", - ); - expect(updateResponse.body.result.structuredContent.title).toBe( - "Updated Title", - ); - - const changeSlugResponse = await mcpFetch<{ - structuredContent: { - ok: true; - slug: string; - previousSlug: string; - }; - }>( - { - jsonrpc: "2.0", - id: "change-slug", - method: "tools/call", - params: { - name: "change_slug", - arguments: { - pageId: createdPage.id, - slug: "mcp-page-final", - }, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in changeSlugResponse.body)) { - throw new Error("Expected change_slug result"); - } - - expect(changeSlugResponse.body.result.structuredContent).toMatchObject({ - ok: true, - slug: "mcp-page-final", - previousSlug: "mcp-page-updated", - }); - - const deleteResponse = await mcpFetch<{ - structuredContent: { - ok: true; - slug: string; - }; - }>( - { - jsonrpc: "2.0", - id: "delete-page", - method: "tools/call", - params: { - name: "delete_page", - arguments: { - pageId: createdPage.id, - }, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in deleteResponse.body)) { - throw new Error("Expected delete_page result"); - } - - expect(deleteResponse.body.result.structuredContent).toEqual({ - ok: true, - slug: "mcp-page-final", - }); - - const deletedPage = await getPageById(createdPage.id); - expect(deletedPage?.deletedAt).not.toBeNull(); - }); - - it("returns a tool error when trying to mutate another user's page", async () => { - await seedPageForUser(testDb, { - id: "other-mcp-page", - userId: otherUser.id, - slug: "other-mcp-slug", - }); - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token); - - const { status, body } = await mcpFetch<{ - isError: boolean; - content: Array<{ text: string }>; - }>( - { - jsonrpc: "2.0", - id: "delete-other-page", - method: "tools/call", - params: { - name: "delete_page", - arguments: { - pageId: "other-mcp-page", - }, - }, - }, - { token, protocolVersion }, - ); - - expect(status).toBe(200); - if (!("result" in body)) { - throw new Error("Expected delete_page tool result"); - } - - expect(body.result.isError).toBe(true); - expect(body.result.content[0]?.text).toBe("Forbidden"); - }); - - it("returns a tool error when the token lacks the required scope", async () => { - const { token } = await issueTokenForUser(testUser, ["mdto:pages:read"]); - const protocolVersion = await initializeMcp(token); - - const { body } = await mcpFetch<{ - isError: boolean; - content: Array<{ text: string }>; - }>( - { - jsonrpc: "2.0", - id: "set-dashboard-visibility-without-scope", - method: "tools/call", - params: { - name: "set_dashboard_visibility", - arguments: { - isDashboardPublic: true, - }, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in body)) { - throw new Error("Expected set_dashboard_visibility result"); - } - - expect(body.result.isError).toBe(true); - expect(body.result.content[0]?.text).toContain("Missing required scope"); - }); - - it("responds to ping after initialize", async () => { - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token); - - const { status, body } = await mcpFetch>( - { - jsonrpc: "2.0", - id: "ping-1", - method: "ping", - }, - { token, protocolVersion }, - ); - - expect(status).toBe(200); - if (!("result" in body)) { - throw new Error("Expected ping result"); - } - - expect(body.result).toEqual({}); - }); - - it("preserves the request id for unsupported methods", async () => { - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token); - - const { status, body } = await mcpFetch( - { - jsonrpc: "2.0", - id: "resources-list-1", - method: "resources/list", - params: {}, - }, - { token, protocolVersion }, - ); - - expect(status).toBe(200); - if (!("error" in body)) { - throw new Error("Expected unsupported method error"); - } - - expect(body.id).toBe("resources-list-1"); - expect(body.error.message).toContain("Method not found"); - }); - - it("reads and updates dashboard visibility", async () => { - await testDb - .update(schema.user) - .set({ isDashboardPublic: false }) - .where(eq(schema.user.id, testUser.id)); - const { token } = await issueTokenForUser(testUser); - const protocolVersion = await initializeMcp(token); - - const getVisibilityResponse = await mcpFetch<{ - structuredContent: { - isDashboardPublic: boolean; - }; - }>( - { - jsonrpc: "2.0", - id: "get-dashboard-visibility", - method: "tools/call", - params: { - name: "get_dashboard_visibility", - arguments: {}, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in getVisibilityResponse.body)) { - throw new Error("Expected get_dashboard_visibility result"); - } - - expect( - getVisibilityResponse.body.result.structuredContent.isDashboardPublic, - ).toBe(false); - - const setVisibilityResponse = await mcpFetch<{ - structuredContent: { - ok: true; - isDashboardPublic: boolean; - }; - }>( - { - jsonrpc: "2.0", - id: "set-dashboard-visibility", - method: "tools/call", - params: { - name: "set_dashboard_visibility", - arguments: { - isDashboardPublic: true, - }, - }, - }, - { token, protocolVersion }, - ); - - if (!("result" in setVisibilityResponse.body)) { - throw new Error("Expected set_dashboard_visibility result"); - } - - expect(setVisibilityResponse.body.result.structuredContent).toEqual({ - ok: true, - isDashboardPublic: true, - }); - - const [updatedUser] = await testDb - .select({ - isDashboardPublic: schema.user.isDashboardPublic, - }) - .from(schema.user) - .where(eq(schema.user.id, testUser.id)) - .limit(1) - .all(); - - expect(updatedUser?.isDashboardPublic).toBe(true); - }); - - it("serves OAuth discovery metadata at the worker root", async () => { - const authorizationServerResponse = await workerFetch<{ - issuer: string; - token_endpoint: string; - }>("http://localhost/.well-known/oauth-authorization-server"); - expect(authorizationServerResponse.status).toBe(200); - expect(authorizationServerResponse.body.issuer).toContain("/api/auth"); - expect(authorizationServerResponse.body.token_endpoint).toContain( - "/api/auth/oauth2/token", - ); - - const protectedResourceResponse = await workerFetch<{ - resource: string; - authorization_servers: string[]; - }>("http://localhost/.well-known/oauth-protected-resource/mcp"); - expect(protectedResourceResponse.status).toBe(200); - expect(protectedResourceResponse.body.resource).toContain("/mcp"); - expect(protectedResourceResponse.body.authorization_servers[0]).toContain( - "/api/auth", - ); - }); -});