Skip to main content
Glama

Karakeep MCP server

by karakeep-app
upload.ts3.57 kB
import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import { Readable } from "stream"; import { pipeline } from "stream/promises"; import { assets, AssetTypes } from "@karakeep/db/schema"; import { QuotaService, StorageQuotaError } from "@karakeep/shared-server"; import { newAssetId, saveAssetFromFile, SUPPORTED_UPLOAD_ASSET_TYPES, } from "@karakeep/shared/assetdb"; import serverConfig from "@karakeep/shared/config"; import { AuthedContext } from "@karakeep/trpc"; const MAX_UPLOAD_SIZE_BYTES = serverConfig.maxAssetSizeMb * 1024 * 1024; // Helper to convert Web Stream to Node Stream (requires Node >= 16.5 / 14.18) export function webStreamToNode( webStream: ReadableStream<Uint8Array>, ): Readable { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any return Readable.fromWeb(webStream as any); // Type assertion might be needed } export function toWebReadableStream( nodeStream: NodeJS.ReadableStream, ): ReadableStream<Uint8Array> { const reader = nodeStream as unknown as Readable; return new ReadableStream({ start(controller) { reader.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk))); reader.on("end", () => controller.close()); reader.on("error", (err) => controller.error(err)); }, }); } export async function uploadAsset( user: AuthedContext["user"], db: AuthedContext["db"], formData: { file: File } | { image: File }, ): Promise< | { error: string; status: 400 | 413 | 403 } | { assetId: string; contentType: string; fileName: string; size: number; } > { let data: File; if ("file" in formData) { data = formData.file; } else { data = formData.image; } const contentType = data.type; // Replace all non-ascii characters with underscores const fileName = data.name.replace(/[^\x20-\x7E]/g, "_"); if (!SUPPORTED_UPLOAD_ASSET_TYPES.has(contentType)) { return { error: "Unsupported asset type", status: 400 }; } if (data.size > MAX_UPLOAD_SIZE_BYTES) { return { error: "Asset is too big", status: 413 }; } let quotaApproved; try { quotaApproved = await QuotaService.checkStorageQuota( db, user.id, data.size, ); } catch (error) { if (error instanceof StorageQuotaError) { return { error: error.message, status: 403 }; } throw error; } let tempFilePath: string | undefined; try { tempFilePath = path.join(os.tmpdir(), `karakeep-upload-${Date.now()}`); await pipeline( webStreamToNode(data.stream()), fs.createWriteStream(tempFilePath), ); const [assetDb] = await db .insert(assets) .values({ id: newAssetId(), // Initially, uploads are uploaded for unknown purpose // And without an attached bookmark. assetType: AssetTypes.UNKNOWN, bookmarkId: null, userId: user.id, contentType, size: data.size, fileName, }) .returning(); await saveAssetFromFile({ userId: user.id, assetId: assetDb.id, assetPath: tempFilePath, metadata: { contentType, fileName }, quotaApproved, }); return { assetId: assetDb.id, contentType, size: data.size, fileName, }; } finally { if ( tempFilePath && (await fs.promises .access(tempFilePath) .then(() => true) .catch(() => false)) ) { await fs.promises.unlink(tempFilePath).catch(() => ({})); } } }

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