Skip to main content
Glama
api.ts12.6 kB
/** * HUMMBL REST API Server * Hono.js-based REST API for mental models access */ import { Hono } from "hono"; import { cors } from "hono/cors"; import { logger } from "hono/logger"; import type { Context, Next } from "hono"; import type { ApiKeyInfo } from "./types/domain.js"; import { TRANSFORMATIONS, PROBLEM_PATTERNS, getAllModels, getModelByCode, getTransformationByKey, searchModels, getModelsByTransformation, } from "./framework/base120.js"; import { isOk, isTransformationType } from "./types/domain.js"; import { validateApiKey } from "./auth/api-keys.js"; import { createD1Client } from "./storage/d1-client.js"; import relationshipsRoutes from "./routes/relationships.js"; import type { ModelRelationship } from "./types/relationships.js"; import type { D1Database, KVNamespace } from "@cloudflare/workers-types"; type Bindings = { DB: D1Database; API_KEYS: KVNamespace; SESSIONS: KVNamespace; }; type Variables = { user?: ApiKeyInfo; }; export type AppContext = Context<{ Bindings: Bindings; Variables: Variables }>; const app = new Hono<{ Bindings: Bindings; Variables: Variables }>(); // Middleware app.use("*", cors()); app.use("*", logger()); // Authentication middleware async function authenticate(c: AppContext, next: Next): Promise<void> { const authHeader = c.req.header("Authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { c.json({ error: "Missing or invalid authorization header" }, 401); return; } const apiKey = authHeader.substring(7); // Remove "Bearer " const authResult = await validateApiKey(c.env.API_KEYS, apiKey); if (!authResult.ok) { c.json({ error: authResult.error.message }, 401); return; } // Store authenticated user info in context for use in routes c.set("user", authResult.value); await next(); } // Health check app.get("/health", (c: AppContext) => { return c.json({ status: "healthy", version: "1.0.0", timestamp: new Date().toISOString(), models_count: 120, }); }); // Get specific model by code app.get("/v1/models/:code", authenticate, async (c: AppContext) => { const code = c.req.param("code").toUpperCase(); const result = getModelByCode(code); if (!isOk(result)) { return c.json({ error: "Model not found" }, 404); } const model = result.value; const transformation = Object.values(TRANSFORMATIONS).find((t) => t.models.some((m) => m.code === model.code) ); // Get enriched data from D1 const db = createD1Client(c.env.DB as any); const enrichedResult = await db.getMentalModel(code); if (!isOk(enrichedResult)) { // Fall back to basic model if not in DB return c.json({ code: model.code, name: model.name, definition: model.definition, priority: model.priority, transformation: transformation?.key ?? null, }); } return c.json(enrichedResult.value); }); // Get relationships for a specific model app.get("/v1/models/:code/relationships", async (c: AppContext) => { try { const code = c.req.param("code").toUpperCase(); const db = createD1Client(c.env.DB as any); const result = await db.getRelationshipsForModel(code); if (!result.ok) { return c.json({ error: result.error }, 500); } return c.json({ model: code, relationships: result.value, }); } catch { return c.json({ error: "Internal server error" }, 500); } }); // Create a new relationship (authenticated) app.post("/v1/relationships", authenticate, async (c: AppContext) => { try { const body = await c.req.json(); const db = createD1Client(c.env.DB as any); // Validate required fields if (!body.source_code || !body.target_code || !body.relationship_type || !body.confidence) { return c.json( { error: "Missing required fields: source_code, target_code, relationship_type, confidence", }, 400 ); } // Validate confidence if (!["A", "B", "C"].includes(body.confidence)) { return c.json({ error: "Confidence must be A, B, or C" }, 400); } const relationshipInput = { source_code: body.source_code.toUpperCase(), target_code: body.target_code.toUpperCase(), relationship_type: body.relationship_type, confidence: body.confidence, evidence: body.evidence, }; const result = await db.createRelationship(relationshipInput); return c.json({ success: true, relationship: result }, 201); } catch { return c.json({ error: "Internal server error" }, 500); } }); // Seed relationships endpoint (admin only) app.post("/v1/relationships/seed", authenticate, async (c: AppContext) => { try { // Check if user has admin permissions const user = c.get("user"); if (!user?.permissions.includes("admin:*")) { return c.json({ error: "Admin permissions required" }, 403); } const db = createD1Client(c.env.DB as any); const { getSeedRelationships } = await import("./data/seed-relationships.js"); const seedData = getSeedRelationships(); let successCount = 0; let errorCount = 0; for (const relationship of seedData) { try { await db.createRelationship(relationship); successCount++; } catch (error) { console.error("Failed to seed relationship:", relationship, error); errorCount++; } } return c.json({ success: true, message: `Seeded ${successCount} relationships, ${errorCount} failed`, seeded: successCount, failed: errorCount, }); } catch { return c.json({ error: "Internal server error" }, 500); } }); // Update relationship (authenticated) app.patch("/v1/relationships/:id", authenticate, async (c: AppContext) => { try { const id = c.req.param("id"); const body = await c.req.json(); const db = createD1Client(c.env.DB as any); // Validate confidence if provided if (body.confidence && !["A", "B", "C"].includes(body.confidence)) { return c.json({ error: "Confidence must be A, B, or C" }, 400); } const updates: Partial<ModelRelationship> = {}; if (body.relationship_type) updates.relationship_type = body.relationship_type; if (body.confidence) updates.confidence = body.confidence; if (body.evidence) updates.logical_derivation = body.evidence; const result = await db.updateRelationship(id, updates); return c.json({ success: true, relationship: result }); } catch { return c.json({ error: "Internal server error" }, 500); } }); // Get single relationship app.get("/v1/relationships/:id", async (c: AppContext) => { try { const id = c.req.param("id"); const db = createD1Client(c.env.DB as any); const result = await db.getRelationship(id); if (!result) { return c.json({ error: "Relationship not found" }, 404); } return c.json(result); } catch { return c.json({ error: "Internal server error" }, 500); } }); // List relationships with filters app.get("/v1/relationships", async (c: AppContext) => { try { const db = createD1Client(c.env.DB as any); const filters = { model: c.req.query("model"), type: c.req.query("type"), confidence: c.req.query("confidence"), status: c.req.query("status"), limit: c.req.query("limit") ? parseInt(c.req.query("limit")!) : undefined, offset: c.req.query("offset") ? parseInt(c.req.query("offset")!) : undefined, }; const relationships = await db.getRelationships(filters); const total = relationships.length; // Note: This is approximate, should be improved return c.json({ relationships, total, limit: filters.limit || 50, offset: filters.offset || 0, }); } catch { return c.json({ error: "Internal server error" }, 500); } }); // List all models with optional transformation filter app.get("/v1/models", authenticate, async (c: AppContext) => { const transformationFilter = c.req.query("transformation"); let models = getAllModels(); if (transformationFilter) { const upperFilter = transformationFilter.toUpperCase(); if (!isTransformationType(upperFilter)) { return c.json({ error: "Invalid transformation filter" }, 400); } const result = getModelsByTransformation(upperFilter); if (!isOk(result)) { return c.json({ error: "Invalid transformation filter" }, 400); } models = result.value; } const enriched = models.map((m) => { const trans = Object.values(TRANSFORMATIONS).find((t) => t.models.some((model) => model.code === m.code) ); return { code: m.code, name: m.name, definition: m.definition, priority: m.priority, transformation: trans?.key ?? "UNKNOWN", }; }); return c.json({ total: enriched.length, models: enriched, }); }); // Search models app.get("/v1/search", authenticate, async (c: AppContext) => { const query = c.req.query("q"); if (!query || query.length < 2) { return c.json({ error: "Query parameter 'q' must be at least 2 characters" }, 400); } const result = searchModels(query); if (!isOk(result)) { return c.json({ error: "Search failed" }, 500); } const enriched = result.value.map((m) => { const trans = Object.values(TRANSFORMATIONS).find((t) => t.models.some((model) => model.code === m.code) ); return { code: m.code, name: m.name, definition: m.definition, priority: m.priority, transformation: trans?.key ?? "UNKNOWN", }; }); return c.json({ query, resultCount: enriched.length, results: enriched, }); }); // Get transformation details app.get("/v1/transformations/:key", authenticate, async (c: AppContext) => { const key = c.req.param("key").toUpperCase(); if (!isTransformationType(key)) { return c.json({ error: "Invalid transformation key" }, 400); } const result = getTransformationByKey(key); if (!isOk(result)) { return c.json({ error: "Transformation not found" }, 404); } const transformation = result.value; return c.json({ key: transformation.key, name: transformation.name, description: transformation.description, modelCount: transformation.models.length, models: transformation.models, }); }); // List all transformations app.get("/v1/transformations", authenticate, (c: AppContext) => { const transformations = Object.values(TRANSFORMATIONS).map((t) => ({ key: t.key, name: t.name, description: t.description, modelCount: t.models.length, })); return c.json({ total: transformations.length, transformations, }); }); // Get recommendations for a problem app.post("/v1/recommend", authenticate, async (c: AppContext) => { const { problem } = await c.req.json(); if (!problem || typeof problem !== "string" || problem.length < 10) { return c.json({ error: "Problem description must be at least 10 characters" }, 400); } const problemLower = problem.toLowerCase(); const matchedPatterns = PROBLEM_PATTERNS.filter((p) => { const patternWords = p.pattern.toLowerCase().split(" "); return patternWords.some((word) => problemLower.includes(word)); }); const recommendations = matchedPatterns.length > 0 ? matchedPatterns : PROBLEM_PATTERNS; const enrichedRecommendations = recommendations.map((rec) => ({ pattern: rec.pattern, transformations: rec.transformations.map((tKey) => { const t = TRANSFORMATIONS[tKey]; return { key: t.key, name: t.name, description: t.description, }; }), topModels: rec.topModels .map((code) => { const result = getModelByCode(code); if (!isOk(result)) { return null; } const model = result.value; return { code: model.code, name: model.name, definition: model.definition, priority: model.priority, }; }) .filter((m): m is NonNullable<typeof m> => m !== null), })); return c.json({ problem, recommendationCount: enrichedRecommendations.length, recommendations: enrichedRecommendations, }); }); // Add relationships routes app.route("/v1", relationshipsRoutes); // Error handling app.onError((err: Error, c: AppContext) => { console.error(`${err}`); return c.json({ error: "Internal server error" }, 500); }); // 404 handler app.notFound((c: AppContext) => { return c.json({ error: "Endpoint not found" }, 404); }); export default app;

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/hummbl-dev/mcp-server'

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