Skip to main content
Glama

Karakeep MCP server

by karakeep-app
schema.ts24.5 kB
import type { AdapterAccount } from "@auth/core/adapters"; import { createId } from "@paralleldrive/cuid2"; import { relations } from "drizzle-orm"; import { AnySQLiteColumn, foreignKey, index, integer, primaryKey, sqliteTable, text, unique, } from "drizzle-orm/sqlite-core"; import { BookmarkTypes } from "@karakeep/shared/types/bookmarks"; function createdAtField() { return integer("createdAt", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()); } function modifiedAtField() { return integer("modifiedAt", { mode: "timestamp" }) .$defaultFn(() => new Date()) .$onUpdate(() => new Date()); } export const users = sqliteTable("user", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), name: text("name").notNull(), email: text("email").notNull().unique(), emailVerified: integer("emailVerified", { mode: "timestamp_ms" }), image: text("image"), password: text("password"), salt: text("salt").notNull().default(""), role: text("role", { enum: ["admin", "user"] }).default("user"), // Admin Only Settings bookmarkQuota: integer("bookmarkQuota"), storageQuota: integer("storageQuota"), browserCrawlingEnabled: integer("browserCrawlingEnabled", { mode: "boolean", }), // User Settings bookmarkClickAction: text("bookmarkClickAction", { enum: ["open_original_link", "expand_bookmark_preview"], }) .notNull() .default("open_original_link"), archiveDisplayBehaviour: text("archiveDisplayBehaviour", { enum: ["show", "hide"], }) .notNull() .default("show"), timezone: text("timezone").default("UTC"), }); export const accounts = sqliteTable( "account", { userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), type: text("type").$type<AdapterAccount["type"]>().notNull(), provider: text("provider").notNull(), providerAccountId: text("providerAccountId").notNull(), refresh_token: text("refresh_token"), access_token: text("access_token"), expires_at: integer("expires_at"), token_type: text("token_type"), scope: text("scope"), id_token: text("id_token"), session_state: text("session_state"), }, (account) => [ primaryKey({ columns: [account.provider, account.providerAccountId], }), ], ); export const sessions = sqliteTable("session", { sessionToken: text("sessionToken") .notNull() .primaryKey() .$defaultFn(() => createId()), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), expires: integer("expires", { mode: "timestamp_ms" }).notNull(), }); export const verificationTokens = sqliteTable( "verificationToken", { identifier: text("identifier").notNull(), token: text("token").notNull(), expires: integer("expires", { mode: "timestamp_ms" }).notNull(), }, (vt) => [primaryKey({ columns: [vt.identifier, vt.token] })], ); export const passwordResetTokens = sqliteTable( "passwordResetToken", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), token: text("token").notNull().unique(), expires: integer("expires", { mode: "timestamp_ms" }).notNull(), createdAt: createdAtField(), }, (prt) => [index("passwordResetTokens_userId_idx").on(prt.userId)], ); export const apiKeys = sqliteTable( "apiKey", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), name: text("name").notNull(), createdAt: createdAtField(), keyId: text("keyId").notNull().unique(), keyHash: text("keyHash").notNull(), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), }, (ak) => [unique().on(ak.name, ak.userId)], ); export const bookmarks = sqliteTable( "bookmarks", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), createdAt: createdAtField(), modifiedAt: modifiedAtField(), title: text("title"), archived: integer("archived", { mode: "boolean" }).notNull().default(false), favourited: integer("favourited", { mode: "boolean" }) .notNull() .default(false), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), taggingStatus: text("taggingStatus", { enum: ["pending", "failure", "success"], }).default("pending"), summarizationStatus: text("summarizationStatus", { enum: ["pending", "failure", "success"], }).default("pending"), summary: text("summary"), note: text("note"), type: text("type", { enum: [BookmarkTypes.LINK, BookmarkTypes.TEXT, BookmarkTypes.ASSET], }).notNull(), source: text("source", { enum: [ "api", "web", "extension", "cli", "mobile", "singlefile", "rss", "import", ], }), }, (b) => [ index("bookmarks_userId_idx").on(b.userId), index("bookmarks_archived_idx").on(b.archived), index("bookmarks_favourited_idx").on(b.favourited), index("bookmarks_createdAt_idx").on(b.createdAt), ], ); export const bookmarkLinks = sqliteTable( "bookmarkLinks", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()) .references(() => bookmarks.id, { onDelete: "cascade" }), url: text("url").notNull(), // Crawled info title: text("title"), description: text("description"), author: text("author"), publisher: text("publisher"), datePublished: integer("datePublished", { mode: "timestamp" }), dateModified: integer("dateModified", { mode: "timestamp" }), imageUrl: text("imageUrl"), favicon: text("favicon"), htmlContent: text("htmlContent"), contentAssetId: text("contentAssetId"), crawledAt: integer("crawledAt", { mode: "timestamp" }), crawlStatus: text("crawlStatus", { enum: ["pending", "failure", "success"], }).default("pending"), crawlStatusCode: integer("crawlStatusCode").default(200), }, (bl) => [index("bookmarkLinks_url_idx").on(bl.url)], ); export const enum AssetTypes { LINK_BANNER_IMAGE = "linkBannerImage", LINK_SCREENSHOT = "linkScreenshot", ASSET_SCREENSHOT = "assetScreenshot", LINK_FULL_PAGE_ARCHIVE = "linkFullPageArchive", LINK_PRECRAWLED_ARCHIVE = "linkPrecrawledArchive", LINK_VIDEO = "linkVideo", LINK_HTML_CONTENT = "linkHtmlContent", BOOKMARK_ASSET = "bookmarkAsset", UNKNOWN = "unknown", } export const assets = sqliteTable( "assets", { // Asset ids don't have a default function as they are generated by the caller id: text("id").notNull().primaryKey(), assetType: text("assetType", { enum: [ AssetTypes.LINK_BANNER_IMAGE, AssetTypes.LINK_SCREENSHOT, AssetTypes.ASSET_SCREENSHOT, AssetTypes.LINK_FULL_PAGE_ARCHIVE, AssetTypes.LINK_PRECRAWLED_ARCHIVE, AssetTypes.LINK_VIDEO, AssetTypes.LINK_HTML_CONTENT, AssetTypes.BOOKMARK_ASSET, AssetTypes.UNKNOWN, ], }).notNull(), size: integer("size").notNull().default(0), contentType: text("contentType"), fileName: text("fileName"), bookmarkId: text("bookmarkId").references(() => bookmarks.id, { onDelete: "cascade", }), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), }, (tb) => [ index("assets_bookmarkId_idx").on(tb.bookmarkId), index("assets_assetType_idx").on(tb.assetType), index("assets_userId_idx").on(tb.userId), ], ); export const highlights = sqliteTable( "highlights", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), bookmarkId: text("bookmarkId") .notNull() .references(() => bookmarks.id, { onDelete: "cascade", }), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), startOffset: integer("startOffset").notNull(), endOffset: integer("endOffset").notNull(), color: text("color", { enum: ["red", "green", "blue", "yellow"], }) .default("yellow") .notNull(), text: text("text"), note: text("note"), createdAt: createdAtField(), }, (tb) => [ index("highlights_bookmarkId_idx").on(tb.bookmarkId), index("highlights_userId_idx").on(tb.userId), ], ); export const bookmarkTexts = sqliteTable("bookmarkTexts", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()) .references(() => bookmarks.id, { onDelete: "cascade" }), text: text("text"), sourceUrl: text("sourceUrl"), }); export const bookmarkAssets = sqliteTable("bookmarkAssets", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()) .references(() => bookmarks.id, { onDelete: "cascade" }), assetType: text("assetType", { enum: ["image", "pdf"] }).notNull(), assetId: text("assetId").notNull(), content: text("content"), metadata: text("metadata"), fileName: text("fileName"), sourceUrl: text("sourceUrl"), }); export const bookmarkTags = sqliteTable( "bookmarkTags", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), name: text("name").notNull(), createdAt: createdAtField(), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), }, (bt) => [ unique().on(bt.userId, bt.name), unique("bookmarkTags_userId_id_idx").on(bt.userId, bt.id), index("bookmarkTags_name_idx").on(bt.name), index("bookmarkTags_userId_idx").on(bt.userId), ], ); export const tagsOnBookmarks = sqliteTable( "tagsOnBookmarks", { bookmarkId: text("bookmarkId") .notNull() .references(() => bookmarks.id, { onDelete: "cascade" }), tagId: text("tagId") .notNull() .references(() => bookmarkTags.id, { onDelete: "cascade" }), attachedAt: integer("attachedAt", { mode: "timestamp" }).$defaultFn( () => new Date(), ), attachedBy: text("attachedBy", { enum: ["ai", "human"] }).notNull(), }, (tb) => [ primaryKey({ columns: [tb.bookmarkId, tb.tagId] }), index("tagsOnBookmarks_tagId_idx").on(tb.tagId), index("tagsOnBookmarks_bookmarkId_idx").on(tb.bookmarkId), ], ); export const bookmarkLists = sqliteTable( "bookmarkLists", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), name: text("name").notNull(), description: text("description"), icon: text("icon").notNull(), createdAt: createdAtField(), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), type: text("type", { enum: ["manual", "smart"] }).notNull(), // Only applicable for smart lists query: text("query"), parentId: text("parentId").references( (): AnySQLiteColumn => bookmarkLists.id, { onDelete: "set null" }, ), // Whoever have access to this token can read the content of this list rssToken: text("rssToken"), public: integer("public", { mode: "boolean" }).notNull().default(false), }, (bl) => [ index("bookmarkLists_userId_idx").on(bl.userId), unique("bookmarkLists_userId_id_idx").on(bl.userId, bl.id), ], ); export const bookmarksInLists = sqliteTable( "bookmarksInLists", { bookmarkId: text("bookmarkId") .notNull() .references(() => bookmarks.id, { onDelete: "cascade" }), listId: text("listId") .notNull() .references(() => bookmarkLists.id, { onDelete: "cascade" }), addedAt: integer("addedAt", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (tb) => [ primaryKey({ columns: [tb.bookmarkId, tb.listId] }), index("bookmarksInLists_bookmarkId_idx").on(tb.bookmarkId), index("bookmarksInLists_listId_idx").on(tb.listId), ], ); export const customPrompts = sqliteTable( "customPrompts", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), text: text("text").notNull(), enabled: integer("enabled", { mode: "boolean" }).notNull(), appliesTo: text("appliesTo", { enum: ["all_tagging", "text", "images", "summary"], }).notNull(), createdAt: createdAtField(), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), }, (bl) => [index("customPrompts_userId_idx").on(bl.userId)], ); export const rssFeedsTable = sqliteTable( "rssFeeds", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), name: text("name").notNull(), url: text("url").notNull(), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), createdAt: createdAtField(), lastFetchedAt: integer("lastFetchedAt", { mode: "timestamp" }), lastFetchedStatus: text("lastFetchedStatus", { enum: ["pending", "failure", "success"], }).default("pending"), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), }, (bl) => [index("rssFeeds_userId_idx").on(bl.userId)], ); export const webhooksTable = sqliteTable( "webhooks", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), createdAt: createdAtField(), url: text("url").notNull(), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), events: text("events", { mode: "json" }) .notNull() .$type<("created" | "edited" | "crawled" | "ai tagged" | "deleted")[]>(), token: text("token"), }, (bl) => [index("webhooks_userId_idx").on(bl.userId)], ); export const rssFeedImportsTable = sqliteTable( "rssFeedImports", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), createdAt: createdAtField(), entryId: text("entryId").notNull(), rssFeedId: text("rssFeedId") .notNull() .references(() => rssFeedsTable.id, { onDelete: "cascade" }), bookmarkId: text("bookmarkId").references(() => bookmarks.id, { onDelete: "set null", }), }, (bl) => [ index("rssFeedImports_feedIdIdx_idx").on(bl.rssFeedId), index("rssFeedImports_entryIdIdx_idx").on(bl.entryId), unique().on(bl.rssFeedId, bl.entryId), ], ); export const config = sqliteTable("config", { key: text("key").notNull().primaryKey(), value: text("value").notNull(), }); export const ruleEngineRulesTable = sqliteTable( "ruleEngineRules", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), name: text("name").notNull(), description: text("description"), event: text("event").notNull(), condition: text("condition").notNull(), // References userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), listId: text("listId"), tagId: text("tagId"), }, (rl) => [ index("ruleEngine_userId_idx").on(rl.userId), // Ensures correct ownership foreignKey({ columns: [rl.userId, rl.tagId], foreignColumns: [bookmarkTags.userId, bookmarkTags.id], name: "ruleEngineRules_userId_tagId_fk", }).onDelete("cascade"), foreignKey({ columns: [rl.userId, rl.listId], foreignColumns: [bookmarkLists.userId, bookmarkLists.id], name: "ruleEngineRules_userId_listId_fk", }).onDelete("cascade"), ], ); export const ruleEngineActionsTable = sqliteTable( "ruleEngineActions", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), ruleId: text("ruleId") .notNull() .references(() => ruleEngineRulesTable.id, { onDelete: "cascade" }), action: text("action").notNull(), // References listId: text("listId"), tagId: text("tagId"), }, (rl) => [ index("ruleEngineActions_userId_idx").on(rl.userId), index("ruleEngineActions_ruleId_idx").on(rl.ruleId), // Ensures correct ownership foreignKey({ columns: [rl.userId, rl.tagId], foreignColumns: [bookmarkTags.userId, bookmarkTags.id], name: "ruleEngineActions_userId_tagId_fk", }).onDelete("cascade"), foreignKey({ columns: [rl.userId, rl.listId], foreignColumns: [bookmarkLists.userId, bookmarkLists.id], name: "ruleEngineActions_userId_listId_fk", }).onDelete("cascade"), ], ); export const invites = sqliteTable("invites", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), email: text("email").notNull(), token: text("token").notNull().unique(), createdAt: createdAtField(), usedAt: integer("usedAt", { mode: "timestamp" }), invitedBy: text("invitedBy") .notNull() .references(() => users.id, { onDelete: "cascade" }), }); export const subscriptions = sqliteTable( "subscriptions", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }) .unique(), stripeCustomerId: text("stripeCustomerId").notNull(), stripeSubscriptionId: text("stripeSubscriptionId"), status: text("status", { enum: [ "active", "canceled", "past_due", "unpaid", "incomplete", "trialing", "incomplete_expired", "paused", ], }).notNull(), tier: text("tier", { enum: ["free", "paid"], }) .notNull() .default("free"), priceId: text("priceId"), cancelAtPeriodEnd: integer("cancelAtPeriodEnd", { mode: "boolean", }).default(false), startDate: integer("startDate", { mode: "timestamp" }), endDate: integer("endDate", { mode: "timestamp" }), createdAt: createdAtField(), modifiedAt: modifiedAtField(), }, (s) => [ index("subscriptions_userId_idx").on(s.userId), index("subscriptions_stripeCustomerId_idx").on(s.stripeCustomerId), ], ); export const importSessions = sqliteTable( "importSessions", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), name: text("name").notNull(), userId: text("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), message: text("message"), rootListId: text("rootListId").references(() => bookmarkLists.id, { onDelete: "set null", }), createdAt: createdAtField(), modifiedAt: modifiedAtField(), }, (is) => [index("importSessions_userId_idx").on(is.userId)], ); export const importSessionBookmarks = sqliteTable( "importSessionBookmarks", { id: text("id") .notNull() .primaryKey() .$defaultFn(() => createId()), importSessionId: text("importSessionId") .notNull() .references(() => importSessions.id, { onDelete: "cascade" }), bookmarkId: text("bookmarkId") .notNull() .references(() => bookmarks.id, { onDelete: "cascade" }), createdAt: createdAtField(), }, (isb) => [ index("importSessionBookmarks_sessionId_idx").on(isb.importSessionId), index("importSessionBookmarks_bookmarkId_idx").on(isb.bookmarkId), unique().on(isb.importSessionId, isb.bookmarkId), ], ); // Relations export const userRelations = relations(users, ({ many, one }) => ({ tags: many(bookmarkTags), bookmarks: many(bookmarks), webhooks: many(webhooksTable), rules: many(ruleEngineRulesTable), invites: many(invites), subscription: one(subscriptions), importSessions: many(importSessions), })); export const bookmarkRelations = relations(bookmarks, ({ many, one }) => ({ user: one(users, { fields: [bookmarks.userId], references: [users.id], }), link: one(bookmarkLinks, { fields: [bookmarks.id], references: [bookmarkLinks.id], }), text: one(bookmarkTexts, { fields: [bookmarks.id], references: [bookmarkTexts.id], }), asset: one(bookmarkAssets, { fields: [bookmarks.id], references: [bookmarkAssets.id], }), tagsOnBookmarks: many(tagsOnBookmarks), bookmarksInLists: many(bookmarksInLists), assets: many(assets), rssFeeds: many(rssFeedImportsTable), importSessionBookmarks: many(importSessionBookmarks), })); export const assetRelations = relations(assets, ({ one }) => ({ bookmark: one(bookmarks, { fields: [assets.bookmarkId], references: [bookmarks.id], }), })); export const bookmarkTagsRelations = relations( bookmarkTags, ({ many, one }) => ({ user: one(users, { fields: [bookmarkTags.userId], references: [users.id], }), tagsOnBookmarks: many(tagsOnBookmarks), }), ); export const tagsOnBookmarksRelations = relations( tagsOnBookmarks, ({ one }) => ({ tag: one(bookmarkTags, { fields: [tagsOnBookmarks.tagId], references: [bookmarkTags.id], }), bookmark: one(bookmarks, { fields: [tagsOnBookmarks.bookmarkId], references: [bookmarks.id], }), }), ); export const apiKeyRelations = relations(apiKeys, ({ one }) => ({ user: one(users, { fields: [apiKeys.userId], references: [users.id], }), })); export const bookmarkListsRelations = relations( bookmarkLists, ({ one, many }) => ({ bookmarksInLists: many(bookmarksInLists), user: one(users, { fields: [bookmarkLists.userId], references: [users.id], }), parent: one(bookmarkLists, { fields: [bookmarkLists.parentId], references: [bookmarkLists.id], }), }), ); export const bookmarksInListsRelations = relations( bookmarksInLists, ({ one }) => ({ bookmark: one(bookmarks, { fields: [bookmarksInLists.bookmarkId], references: [bookmarks.id], }), list: one(bookmarkLists, { fields: [bookmarksInLists.listId], references: [bookmarkLists.id], }), }), ); export const webhooksRelations = relations(webhooksTable, ({ one }) => ({ user: one(users, { fields: [webhooksTable.userId], references: [users.id], }), })); export const ruleEngineRulesRelations = relations( ruleEngineRulesTable, ({ one, many }) => ({ user: one(users, { fields: [ruleEngineRulesTable.userId], references: [users.id], }), actions: many(ruleEngineActionsTable), }), ); export const ruleEngineActionsTableRelations = relations( ruleEngineActionsTable, ({ one }) => ({ rule: one(ruleEngineRulesTable, { fields: [ruleEngineActionsTable.ruleId], references: [ruleEngineRulesTable.id], }), }), ); export const rssFeedImportsTableRelations = relations( rssFeedImportsTable, ({ one }) => ({ rssFeed: one(rssFeedsTable, { fields: [rssFeedImportsTable.rssFeedId], references: [rssFeedsTable.id], }), bookmark: one(bookmarks, { fields: [rssFeedImportsTable.bookmarkId], references: [bookmarks.id], }), }), ); export const invitesRelations = relations(invites, ({ one }) => ({ invitedBy: one(users, { fields: [invites.invitedBy], references: [users.id], }), })); export const subscriptionsRelations = relations(subscriptions, ({ one }) => ({ user: one(users, { fields: [subscriptions.userId], references: [users.id], }), })); export const passwordResetTokensRelations = relations( passwordResetTokens, ({ one }) => ({ user: one(users, { fields: [passwordResetTokens.userId], references: [users.id], }), }), ); export const importSessionsRelations = relations( importSessions, ({ one, many }) => ({ user: one(users, { fields: [importSessions.userId], references: [users.id], }), bookmarks: many(importSessionBookmarks), }), ); export const importSessionBookmarksRelations = relations( importSessionBookmarks, ({ one }) => ({ importSession: one(importSessions, { fields: [importSessionBookmarks.importSessionId], references: [importSessions.id], }), bookmark: one(bookmarks, { fields: [importSessionBookmarks.bookmarkId], references: [bookmarks.id], }), }), );

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/karakeep-app/karakeep'

If you have feedback or need assistance with the MCP directory API, please join our Discord server