Skip to main content
Glama

Karakeep MCP server

by karakeep-app
index.ts4.66 kB
import type { Index } from "meilisearch"; import { MeiliSearch } from "meilisearch"; import type { BookmarkSearchDocument, FilterQuery, SearchIndexClient, SearchOptions, SearchResponse, } from "@karakeep/shared/search"; import { PluginProvider } from "@karakeep/shared/plugins"; import { envConfig } from "./env"; function filterToMeiliSearchFilter(filter: FilterQuery): string { switch (filter.type) { case "eq": return `${filter.field} = "${filter.value}"`; case "in": return `${filter.field} IN [${filter.values.join(",")}]`; default: { const exhaustiveCheck: never = filter; throw new Error(`Unhandled color case: ${exhaustiveCheck}`); } } } class MeiliSearchIndexClient implements SearchIndexClient { constructor(private index: Index<BookmarkSearchDocument>) {} async addDocuments(documents: BookmarkSearchDocument[]): Promise<void> { const task = await this.index.addDocuments(documents, { primaryKey: "id", }); await this.ensureTaskSuccess(task.taskUid); } async deleteDocuments(ids: string[]): Promise<void> { const task = await this.index.deleteDocuments(ids); await this.ensureTaskSuccess(task.taskUid); } async search(options: SearchOptions): Promise<SearchResponse> { const result = await this.index.search(options.query, { filter: options.filter?.map((f) => filterToMeiliSearchFilter(f)), limit: options.limit, offset: options.offset, sort: options.sort?.map((s) => `${s.field}:${s.order}`), attributesToRetrieve: ["id"], showRankingScore: true, }); return { hits: result.hits.map((hit) => ({ id: hit.id, score: hit._rankingScore, })), totalHits: result.estimatedTotalHits ?? 0, processingTimeMs: result.processingTimeMs, }; } async clearIndex(): Promise<void> { const task = await this.index.deleteAllDocuments(); await this.ensureTaskSuccess(task.taskUid); } private async ensureTaskSuccess(taskUid: number): Promise<void> { const task = await this.index.waitForTask(taskUid, { intervalMs: 200, }); if (task.error) { throw new Error(`Search task failed: ${task.error.message}`); } } } export class MeiliSearchProvider implements PluginProvider<SearchIndexClient> { private client: MeiliSearch | undefined; private indexClient: SearchIndexClient | undefined; private readonly indexName = "bookmarks"; constructor() { if (MeiliSearchProvider.isConfigured()) { this.client = new MeiliSearch({ host: envConfig.MEILI_ADDR!, apiKey: envConfig.MEILI_MASTER_KEY, }); } } static isConfigured(): boolean { return !!envConfig.MEILI_ADDR; } async getClient(): Promise<SearchIndexClient | null> { if (this.indexClient) { return this.indexClient; } if (!this.client) { return null; } const indices = await this.client.getIndexes(); let indexFound = indices.results.find((i) => i.uid === this.indexName); if (!indexFound) { const idx = await this.client.createIndex(this.indexName, { primaryKey: "id", }); await this.client.waitForTask(idx.taskUid); indexFound = await this.client.getIndex<BookmarkSearchDocument>( this.indexName, ); } await this.configureIndex(indexFound); this.indexClient = new MeiliSearchIndexClient(indexFound); return this.indexClient; } private async configureIndex( index: Index<BookmarkSearchDocument>, ): Promise<void> { const desiredFilterableAttributes = ["id", "userId"].sort(); const desiredSortableAttributes = ["createdAt"].sort(); const settings = await index.getSettings(); if ( JSON.stringify(settings.filterableAttributes?.sort()) !== JSON.stringify(desiredFilterableAttributes) ) { console.log( `[meilisearch] Updating desired filterable attributes to ${desiredFilterableAttributes} from ${settings.filterableAttributes}`, ); const taskId = await index.updateFilterableAttributes( desiredFilterableAttributes, ); await this.client!.waitForTask(taskId.taskUid); } if ( JSON.stringify(settings.sortableAttributes?.sort()) !== JSON.stringify(desiredSortableAttributes) ) { console.log( `[meilisearch] Updating desired sortable attributes to ${desiredSortableAttributes} from ${settings.sortableAttributes}`, ); const taskId = await index.updateSortableAttributes( desiredSortableAttributes, ); await this.client!.waitForTask(taskId.taskUid); } } }

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