Skip to main content
Glama
fadlee

PocketBase MCP Server

by fadlee
generation.ts7.93 kB
import type PocketBase from "pocketbase"; import type { ToolHandler, GeneratePbSchemaArgs, GenerateTypescriptInterfacesArgs } from "../../types/index.js"; import { handlePocketBaseError } from "../../utils/errors.js"; import { createTextResponse, createJsonResponse } from "../../utils/response.js"; import { analyzeTypeScriptForSchema, toPascalCase, mapPocketBaseTypeToTypeScript } from "../../utils/typescript.js"; /** * Generate a PocketBase schema based on TypeScript interfaces or database diagram */ export function createGeneratePbSchemaHandler(pb: PocketBase): ToolHandler { return async (args: GeneratePbSchemaArgs) => { try { const { sourceCode, options = {} } = args; const includeAuth = options.includeAuthentication ?? true; const includeTimestamps = options.includeTimestamps ?? true; // Analyze TypeScript source code const interfaces = analyzeTypeScriptForSchema(sourceCode, options); if (interfaces.length === 0) { return createTextResponse("No TypeScript interfaces found in the provided source code."); } const collections = []; // Generate collections from interfaces for (const iface of interfaces) { const fields = []; // Add standard fields if requested if (includeTimestamps) { fields.push( { name: "created", type: "autodate", required: false, system: true, onCreate: true, onUpdate: false, }, { name: "updated", type: "autodate", required: false, system: true, onCreate: true, onUpdate: true, } ); } // Convert interface properties to PocketBase fields for (const prop of iface.properties) { const field: any = { name: prop.name, type: mapTypeScriptToPocketBase(prop.type), required: !prop.optional, }; // Add type-specific options if (field.type === "text" && prop.type.includes("email")) { field.type = "email"; } else if (field.type === "text" && prop.type.includes("url")) { field.type = "url"; } else if (field.type === "text" && prop.type.includes("Date")) { field.type = "date"; } fields.push(field); } const collection = { name: iface.name.toLowerCase(), type: "base" as const, fields, listRule: "", viewRule: "", createRule: "", updateRule: "", deleteRule: "", }; collections.push(collection); } // Add authentication collection if requested if (includeAuth) { const authCollection = { name: "users", type: "auth" as const, fields: [ { name: "name", type: "text", required: false, }, { name: "avatar", type: "file", required: false, options: { maxSelect: 1, maxSize: 5242880, mimeTypes: ["image/jpeg", "image/png", "image/svg+xml", "image/gif"], }, }, ], listRule: "id = @request.auth.id", viewRule: "id = @request.auth.id", createRule: "", updateRule: "id = @request.auth.id", deleteRule: "id = @request.auth.id", }; collections.unshift(authCollection); } const schema = { collections, generatedAt: new Date().toISOString(), source: "TypeScript interfaces", }; return createJsonResponse(schema); } catch (error: unknown) { throw handlePocketBaseError("generate PocketBase schema", error); } }; } /** * Generate TypeScript interfaces from PocketBase collections */ export function createGenerateTypescriptInterfacesHandler(pb: PocketBase): ToolHandler { return async (args: GenerateTypescriptInterfacesArgs = {}) => { try { const { collections: targetCollections = [], options = {} } = args; const includeRelations = options.includeRelations ?? true; // Get all collections or specific ones const allCollections = await pb.collections.getFullList(); const collectionsToProcess = targetCollections.length > 0 ? allCollections.filter(c => targetCollections.includes(c.name)) : allCollections; const interfaces: string[] = []; // Generate base record interface interfaces.push("// Base record interface"); interfaces.push("export interface BaseRecord {"); interfaces.push(" id: string;"); interfaces.push(" created: string;"); interfaces.push(" updated: string;"); interfaces.push("}"); interfaces.push(""); // Generate interfaces for each collection for (const collection of collectionsToProcess) { const interfaceName = toPascalCase(collection.name); const fields = collection.fields || []; interfaces.push(`// ${collection.name} collection`); interfaces.push(`export interface ${interfaceName} extends BaseRecord {`); for (const field of fields) { if (field.system) continue; // Skip system fields const fieldType = mapPocketBaseTypeToTypeScript(field.type, field); const optional = !field.required ? "?" : ""; if (includeRelations && field.type === "relation") { const relatedCollection = field.options?.collectionId; if (relatedCollection) { const relatedCollectionName = allCollections.find( c => c.id === relatedCollection )?.name; if (relatedCollectionName) { const relatedInterface = toPascalCase(relatedCollectionName); const isMultiple = field.options?.maxSelect !== 1; interfaces.push( ` ${field.name}${optional}: ${isMultiple ? `${relatedInterface}[]` : relatedInterface};` ); continue; } } } interfaces.push(` ${field.name}${optional}: ${fieldType};`); } interfaces.push("}"); interfaces.push(""); } // Add utility types interfaces.push("// Utility types"); interfaces.push("export type RecordId = string;"); interfaces.push("export type RecordTimestamp = string;"); interfaces.push(""); // Add collection names type const collectionNames = collectionsToProcess.map(c => `"${c.name}"`).join(" | "); interfaces.push(`export type CollectionName = ${collectionNames};`); const result = interfaces.join("\n"); return createTextResponse(result); } catch (error: unknown) { throw handlePocketBaseError("generate TypeScript interfaces", error); } }; } /** * Helper function to map TypeScript types to PocketBase field types */ function mapTypeScriptToPocketBase(tsType: string): string { const type = tsType.toLowerCase(); if (type.includes("string")) return "text"; if (type.includes("number")) return "number"; if (type.includes("boolean")) return "bool"; if (type.includes("date")) return "date"; if (type.includes("email")) return "email"; if (type.includes("url")) return "url"; if (type.includes("[]")) return "json"; if (type.includes("object") || type.includes("{")) return "json"; return "text"; // Default fallback }

Implementation Reference

Latest Blog Posts

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/fadlee/pocketbase-mcp'

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