Skip to main content
Glama
embedding-engine.test.ts28.3 kB
/** * Embedding Engine Tests * * Tests for five-sector embedding generation system. * Tests cover: * - Episodic embedding generation from temporal content * - Semantic embedding generation from factual content * - Procedural embedding generation from process descriptions * - Emotional embedding generation from affective content * - Reflective embedding generation from meta-insights * - Embedding dimension consistency * - Request deduplication * - Batch operations * - Cache efficiency * * Requirements: 2.1, 2.2, 2.3, 2.4, 2.5 */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { EmbeddingCache } from "../../../embeddings/cache"; import { EmbeddingEngine } from "../../../embeddings/embedding-engine"; import { MemorySector, type EmotionState, type MemoryContent, type TemporalContext, } from "../../../embeddings/types"; import { MockOllamaEmbeddingModel } from "../../utils/mock-embeddings"; // Helper function for cosine similarity calculation function calculateCosineSimilarity(a: number[], b: number[]): number { if (a.length !== b.length) { throw new Error("Embeddings must have same dimension"); } let dotProduct = 0; let magnitudeA = 0; let magnitudeB = 0; for (let i = 0; i < a.length; i++) { dotProduct += a[i] * b[i]; magnitudeA += a[i] * a[i]; magnitudeB += b[i] * b[i]; } magnitudeA = Math.sqrt(magnitudeA); magnitudeB = Math.sqrt(magnitudeB); if (magnitudeA === 0 || magnitudeB === 0) { return 0; } return dotProduct / (magnitudeA * magnitudeB); } describe("EmbeddingEngine - Episodic Embeddings", () => { let engine: EmbeddingEngine; const expectedDimension = 768; // nomic-embed-text dimension beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate episodic embedding from temporal content", async () => { const content = "Met with the team at the office to discuss Q4 planning"; const context: TemporalContext = { timestamp: new Date("2024-11-10T14:00:00Z"), sessionId: "test-session-1", duration: 3600, location: "Office Conference Room A", participants: ["Alice", "Bob", "Charlie"], }; const embedding = await engine.generateEpisodicEmbedding(content, context); expect(embedding).toBeDefined(); expect(Array.isArray(embedding)).toBe(true); expect(embedding.length).toBe(expectedDimension); expect(embedding.every((val) => typeof val === "number" && !isNaN(val))).toBe(true); const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); expect(magnitude).toBeCloseTo(1.0, 5); }); it("should generate different embeddings for different temporal contexts", async () => { const content = "Team meeting"; const context1: TemporalContext = { timestamp: new Date("2024-11-10T09:00:00Z"), sessionId: "test-session-2", location: "Office", }; const context2: TemporalContext = { timestamp: new Date("2024-11-10T15:00:00Z"), sessionId: "test-session-3", location: "Remote", }; const embedding1 = await engine.generateEpisodicEmbedding(content, context1); const embedding2 = await engine.generateEpisodicEmbedding(content, context2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeLessThan(1.0); }); it("should handle episodic with minimal context", async () => { const content = "Quick standup meeting"; const context: TemporalContext = { timestamp: new Date(), sessionId: "test-session-4", }; const embedding = await engine.generateEpisodicEmbedding(content, context); expect(embedding).toBeDefined(); expect(embedding.length).toBe(expectedDimension); }); }); describe("EmbeddingEngine - Semantic Embeddings", () => { let engine: EmbeddingEngine; const expectedDimension = 768; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate semantic embedding from factual content", async () => { const content = "PostgreSQL is a powerful, open-source relational database management system that supports advanced data types and performance optimization features."; const embedding = await engine.generateSemanticEmbedding(content); expect(embedding).toBeDefined(); expect(Array.isArray(embedding)).toBe(true); expect(embedding.length).toBe(expectedDimension); expect(embedding.every((val) => typeof val === "number" && !isNaN(val))).toBe(true); const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); expect(magnitude).toBeCloseTo(1.0, 5); }); it("should generate similar embeddings for semantically similar content", async () => { const content1 = "Machine learning is a subset of artificial intelligence"; const content2 = "AI includes machine learning as one of its core components"; const embedding1 = await engine.generateSemanticEmbedding(content1); const embedding2 = await engine.generateSemanticEmbedding(content2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeGreaterThan(0.7); }); it("should generate different embeddings for semantically different content", async () => { const content1 = "Database management systems store and retrieve data efficiently"; const content2 = "Quantum computing uses quantum bits for parallel computation"; const embedding1 = await engine.generateSemanticEmbedding(content1); const embedding2 = await engine.generateSemanticEmbedding(content2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeLessThan(0.9); }); it("should handle short factual statements", async () => { const content = "Water boils at 100 degrees Celsius"; const embedding = await engine.generateSemanticEmbedding(content); expect(embedding).toBeDefined(); expect(embedding.length).toBe(expectedDimension); }); }); describe("EmbeddingEngine - Procedural Embeddings", () => { let engine: EmbeddingEngine; const expectedDimension = 768; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate procedural embedding from process descriptions", async () => { const content = `To deploy the application: 1. Build the Docker image using 'docker build -t app:latest .' 2. Push the image to the registry with 'docker push app:latest' 3. Update the Kubernetes deployment with 'kubectl apply -f deployment.yaml' 4. Verify the deployment with 'kubectl rollout status deployment/app'`; const embedding = await engine.generateProceduralEmbedding(content); expect(embedding).toBeDefined(); expect(Array.isArray(embedding)).toBe(true); expect(embedding.length).toBe(expectedDimension); expect(embedding.every((val) => typeof val === "number" && !isNaN(val))).toBe(true); const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); expect(magnitude).toBeCloseTo(1.0, 5); }); it("should generate similar embeddings for similar procedures", async () => { const content1 = "To make coffee: 1. Boil water 2. Add coffee grounds 3. Pour water 4. Wait 5 minutes"; const content2 = "Coffee preparation: Heat water, add grounds, pour hot water, steep for 5 minutes"; const embedding1 = await engine.generateProceduralEmbedding(content1); const embedding2 = await engine.generateProceduralEmbedding(content2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeGreaterThan(0.7); }); it("should generate different embeddings for different procedures", async () => { const content1 = "To bake a cake: Mix ingredients, pour into pan, bake at 350°F for 30 minutes"; const content2 = "To change a tire: Loosen lug nuts, jack up car, remove wheel, install spare"; const embedding1 = await engine.generateProceduralEmbedding(content1); const embedding2 = await engine.generateProceduralEmbedding(content2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeLessThan(0.9); }); it("should handle procedural content with implicit steps", async () => { const content = "When debugging, first reproduce the issue, then isolate the cause, and finally apply a fix"; const embedding = await engine.generateProceduralEmbedding(content); expect(embedding).toBeDefined(); expect(embedding.length).toBe(expectedDimension); }); }); describe("EmbeddingEngine - Emotional Embeddings", () => { let engine: EmbeddingEngine; const expectedDimension = 768; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate emotional embedding from affective content", async () => { const content = "I'm thrilled about the project launch! The team worked incredibly hard and the results exceeded all expectations."; const emotion: EmotionState = { valence: 0.9, arousal: 0.8, dominance: 0.6, primaryEmotion: "joy", }; const embedding = await engine.generateEmotionalEmbedding(content, emotion); expect(embedding).toBeDefined(); expect(Array.isArray(embedding)).toBe(true); expect(embedding.length).toBe(expectedDimension); expect(embedding.every((val) => typeof val === "number" && !isNaN(val))).toBe(true); const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); expect(magnitude).toBeCloseTo(1.0, 5); }); it("should generate different embeddings for different emotional states", async () => { const content = "The project outcome was unexpected"; const joyEmotion: EmotionState = { valence: 0.8, arousal: 0.7, dominance: 0.5, primaryEmotion: "joy", }; const sadnessEmotion: EmotionState = { valence: -0.7, arousal: 0.3, dominance: -0.4, primaryEmotion: "sadness", }; const embedding1 = await engine.generateEmotionalEmbedding(content, joyEmotion); const embedding2 = await engine.generateEmotionalEmbedding(content, sadnessEmotion); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeLessThan(0.9); }); it("should generate similar embeddings for similar emotional states", async () => { const content1 = "I'm excited about the new opportunity"; const content2 = "I'm enthusiastic about this chance"; const emotion1: EmotionState = { valence: 0.8, arousal: 0.7, dominance: 0.5, primaryEmotion: "joy", }; const emotion2: EmotionState = { valence: 0.85, arousal: 0.75, dominance: 0.55, primaryEmotion: "joy", }; const embedding1 = await engine.generateEmotionalEmbedding(content1, emotion1); const embedding2 = await engine.generateEmotionalEmbedding(content2, emotion2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeGreaterThan(0.7); }); it("should handle neutral emotional content", async () => { const content = "The meeting is scheduled for 2 PM"; const emotion: EmotionState = { valence: 0.0, arousal: 0.3, dominance: 0.0, primaryEmotion: "neutral", }; const embedding = await engine.generateEmotionalEmbedding(content, emotion); expect(embedding).toBeDefined(); expect(embedding.length).toBe(expectedDimension); }); }); describe("EmbeddingEngine - Reflective Embeddings", () => { let engine: EmbeddingEngine; const expectedDimension = 768; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate reflective embedding from meta-insights", async () => { const content = "Looking back at the project, I realize that early planning prevented many issues"; const insights = [ "Planning is crucial for project success", "Prevention is better than firefighting", "Early investment saves time later", ]; const embedding = await engine.generateReflectiveEmbedding(content, insights); expect(embedding).toBeDefined(); expect(Array.isArray(embedding)).toBe(true); expect(embedding.length).toBe(expectedDimension); expect(embedding.every((val) => typeof val === "number" && !isNaN(val))).toBe(true); const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); expect(magnitude).toBeCloseTo(1.0, 5); }); it("should generate different embeddings for different insights", async () => { const content = "Reflecting on the experience"; const insights1 = ["Communication is key", "Team collaboration matters"]; const insights2 = ["Technical skills are important", "Code quality is essential"]; const embedding1 = await engine.generateReflectiveEmbedding(content, insights1); const embedding2 = await engine.generateReflectiveEmbedding(content, insights2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeLessThan(0.85); }); it("should generate similar embeddings for related insights", async () => { const content1 = "I learned that testing early catches bugs sooner"; const insights1 = ["Early testing is valuable", "Prevention beats cure"]; const content2 = "Testing throughout development prevents issues"; const insights2 = ["Continuous testing helps quality", "Proactive testing is better"]; const embedding1 = await engine.generateReflectiveEmbedding(content1, insights1); const embedding2 = await engine.generateReflectiveEmbedding(content2, insights2); const similarity = calculateCosineSimilarity(embedding1, embedding2); expect(similarity).toBeGreaterThan(0.6); }); it("should handle reflective content with minimal insights", async () => { const content = "This experience taught me patience"; const insights = ["Patience is important"]; const embedding = await engine.generateReflectiveEmbedding(content, insights); expect(embedding).toBeDefined(); expect(embedding.length).toBe(expectedDimension); }); }); describe("EmbeddingEngine - Dimension Consistency", () => { let engine: EmbeddingEngine; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should return consistent dimension across all sector types", async () => { const content = "Test content for dimension consistency"; const context: TemporalContext = { timestamp: new Date(), sessionId: "test-session-9" }; const emotion: EmotionState = { valence: 0.5, arousal: 0.5, dominance: 0.0, primaryEmotion: "neutral", }; const insights = ["Test insight"]; const episodic = await engine.generateEpisodicEmbedding(content, context); const semantic = await engine.generateSemanticEmbedding(content); const procedural = await engine.generateProceduralEmbedding(content); const emotional = await engine.generateEmotionalEmbedding(content, emotion); const reflective = await engine.generateReflectiveEmbedding(content, insights); const expectedDim = engine.getModelDimension(); expect(episodic.length).toBe(expectedDim); expect(semantic.length).toBe(expectedDim); expect(procedural.length).toBe(expectedDim); expect(emotional.length).toBe(expectedDim); expect(reflective.length).toBe(expectedDim); }); it("should respect configured embedding dimension", async () => { const dimension = engine.getModelDimension(); expect([768, 1024, 1536]).toContain(dimension); }); it("should generate embeddings with consistent dimension in batch operations", async () => { const memories: MemoryContent[] = [ { text: "Memory 1", sector: MemorySector.Semantic }, { text: "Memory 2", sector: MemorySector.Episodic, context: { timestamp: new Date(), sessionId: "test-session-5" }, }, { text: "Memory 3", sector: MemorySector.Procedural }, ]; const embeddings = await engine.batchGenerateEmbeddings(memories); const expectedDim = engine.getModelDimension(); embeddings.forEach((sectorEmbeddings) => { expect(sectorEmbeddings.episodic.length).toBe(expectedDim); expect(sectorEmbeddings.semantic.length).toBe(expectedDim); expect(sectorEmbeddings.procedural.length).toBe(expectedDim); expect(sectorEmbeddings.emotional.length).toBe(expectedDim); expect(sectorEmbeddings.reflective.length).toBe(expectedDim); }); }); }); describe("EmbeddingEngine - All Sector Generation", () => { let engine: EmbeddingEngine; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate embeddings for all five sectors", async () => { const memory: MemoryContent = { text: "Yesterday I learned a new debugging technique that helped me fix a critical bug. I felt relieved and realized that systematic approaches work better than random trial and error.", sector: MemorySector.Episodic, context: { timestamp: new Date("2024-11-09T16:00:00Z"), sessionId: "test-session-6", location: "Office", }, emotion: { valence: 0.6, arousal: 0.5, dominance: 0.4, primaryEmotion: "relief", }, insights: ["Systematic approaches are more effective", "Debugging requires patience"], }; const embeddings = await engine.generateAllSectorEmbeddings(memory); expect(embeddings.episodic).toBeDefined(); expect(embeddings.semantic).toBeDefined(); expect(embeddings.procedural).toBeDefined(); expect(embeddings.emotional).toBeDefined(); expect(embeddings.reflective).toBeDefined(); expect(Array.isArray(embeddings.episodic)).toBe(true); expect(Array.isArray(embeddings.semantic)).toBe(true); expect(Array.isArray(embeddings.procedural)).toBe(true); expect(Array.isArray(embeddings.emotional)).toBe(true); expect(Array.isArray(embeddings.reflective)).toBe(true); const expectedDim = engine.getModelDimension(); expect(embeddings.episodic.length).toBe(expectedDim); expect(embeddings.semantic.length).toBe(expectedDim); expect(embeddings.procedural.length).toBe(expectedDim); expect(embeddings.emotional.length).toBe(expectedDim); expect(embeddings.reflective.length).toBe(expectedDim); }); it("should generate different embeddings for each sector", async () => { const memory: MemoryContent = { text: "Team meeting about project planning", sector: MemorySector.Episodic, context: { timestamp: new Date(), sessionId: "test-session-7" }, emotion: { valence: 0.5, arousal: 0.5, dominance: 0.0, primaryEmotion: "neutral" }, insights: ["Planning is important"], }; const embeddings = await engine.generateAllSectorEmbeddings(memory); const sectors = [ embeddings.episodic, embeddings.semantic, embeddings.procedural, embeddings.emotional, embeddings.reflective, ]; for (let i = 0; i < sectors.length; i++) { for (let j = i + 1; j < sectors.length; j++) { const similarity = calculateCosineSimilarity(sectors[i], sectors[j]); expect(similarity).toBeLessThan(1.0); } } }); }); describe("EmbeddingEngine - Batch Operations", () => { let engine: EmbeddingEngine; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should generate embeddings for multiple memories in batch", async () => { const memories: MemoryContent[] = [ { text: "First memory", sector: MemorySector.Semantic }, { text: "Second memory", sector: MemorySector.Episodic, context: { timestamp: new Date(), sessionId: "test-session-8" }, }, { text: "Third memory", sector: MemorySector.Procedural }, ]; const embeddings = await engine.batchGenerateEmbeddings(memories); expect(embeddings).toBeDefined(); expect(embeddings.length).toBe(3); embeddings.forEach((sectorEmbeddings) => { expect(sectorEmbeddings.episodic).toBeDefined(); expect(sectorEmbeddings.semantic).toBeDefined(); expect(sectorEmbeddings.procedural).toBeDefined(); expect(sectorEmbeddings.emotional).toBeDefined(); expect(sectorEmbeddings.reflective).toBeDefined(); }); }); it("should handle empty batch gracefully", async () => { const memories: MemoryContent[] = []; const embeddings = await engine.batchGenerateEmbeddings(memories); expect(embeddings).toBeDefined(); expect(embeddings.length).toBe(0); }); it("should handle single item batch", async () => { const memories: MemoryContent[] = [{ text: "Single memory", sector: MemorySector.Semantic }]; const embeddings = await engine.batchGenerateEmbeddings(memories); expect(embeddings).toBeDefined(); expect(embeddings.length).toBe(1); expect(embeddings[0].semantic).toBeDefined(); }); it("should benefit from caching in batch operations", async () => { const memories: MemoryContent[] = [ { text: "Unique content 1", sector: MemorySector.Semantic }, { text: "Duplicate content", sector: MemorySector.Semantic }, { text: "Unique content 2", sector: MemorySector.Semantic }, { text: "Duplicate content", sector: MemorySector.Semantic }, { text: "Duplicate content", sector: MemorySector.Semantic }, ]; const results = await engine.batchGenerateEmbeddings(memories); expect(results).toHaveLength(5); expect(results[1].semantic).toEqual(results[3].semantic); expect(results[1].semantic).toEqual(results[4].semantic); }); }); describe("EmbeddingEngine - Model Management", () => { let engine: EmbeddingEngine; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(); engine = new EmbeddingEngine(model, cache); }); it("should load a different model and clear cache", async () => { const content = "Test content"; const context: TemporalContext = { timestamp: new Date(), sessionId: "test-session", }; await engine.generateEpisodicEmbedding(content, context); const newModel = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "mxbai-embed-large", dimension: 1024, }); engine.loadModel(newModel); expect(engine.getModelDimension()).toBe(1024); }); it("should handle temporal context with sequence number", async () => { const content = "Sequential event"; const context: TemporalContext = { timestamp: new Date(), sessionId: "test-session", sequenceNumber: 5, }; const embedding = await engine.generateEpisodicEmbedding(content, context); expect(embedding).toBeDefined(); expect(embedding.length).toBe(768); }); it("should handle temporal context with duration", async () => { const content = "Event with duration"; const context: TemporalContext = { timestamp: new Date(), sessionId: "test-session", duration: 3600, }; const embedding = await engine.generateEpisodicEmbedding(content, context); expect(embedding).toBeDefined(); expect(embedding.length).toBe(768); }); }); describe("EmbeddingEngine - Request Deduplication", () => { let engine: EmbeddingEngine; let model: MockOllamaEmbeddingModel; beforeEach(() => { model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(10000, 3600000); engine = new EmbeddingEngine(model, cache); }); it("should deduplicate concurrent requests for same content", async () => { const generateSpy = vi.spyOn(model, "generate"); const content = "Duplicate request test content"; const promises = Array.from({ length: 5 }, () => engine.generateSemanticEmbedding(content)); const results = await Promise.all(promises); expect(results).toHaveLength(5); results.forEach((result) => { expect(result).toEqual(results[0]); }); expect(generateSpy).toHaveBeenCalledTimes(1); }); it("should deduplicate concurrent requests across different sectors", async () => { const generateSpy = vi.spyOn(model, "generate"); const memory: MemoryContent = { text: "Multi-sector deduplication test", sector: MemorySector.Semantic, context: { timestamp: new Date(), sessionId: "test-session", }, emotion: { valence: 0.5, arousal: 0.5, dominance: 0.0, }, insights: ["Test insight"], }; const promises = Array.from({ length: 3 }, () => engine.generateAllSectorEmbeddings(memory)); const results = await Promise.all(promises); expect(results).toHaveLength(3); results.forEach((result) => { expect(result).toEqual(results[0]); }); expect(generateSpy.mock.calls.length).toBeLessThanOrEqual(5); }); it("should not deduplicate requests for different content", async () => { const generateSpy = vi.spyOn(model, "generate"); const promises = [ engine.generateSemanticEmbedding("Content A"), engine.generateSemanticEmbedding("Content B"), engine.generateSemanticEmbedding("Content C"), ]; await Promise.all(promises); expect(generateSpy).toHaveBeenCalledTimes(3); }); it("should handle deduplication with cache misses and hits", async () => { const content = "Cache and deduplication test"; await engine.generateSemanticEmbedding(content); const generateSpy = vi.spyOn(model, "generate"); const promises = Array.from({ length: 5 }, () => engine.generateSemanticEmbedding(content)); await Promise.all(promises); expect(generateSpy).not.toHaveBeenCalled(); }); }); describe("EmbeddingEngine - Cache Efficiency", () => { let engine: EmbeddingEngine; beforeEach(() => { const model = new MockOllamaEmbeddingModel({ host: "http://localhost:11434", modelName: "nomic-embed-text", dimension: 768, }); const cache = new EmbeddingCache(10000, 3600000); engine = new EmbeddingEngine(model, cache); }); it("should use cache for repeated requests", async () => { const content = "Cache test content"; const embedding1 = await engine.generateSemanticEmbedding(content); const embedding2 = await engine.generateSemanticEmbedding(content); expect(embedding1).toEqual(embedding2); }); });

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/keyurgolani/ThoughtMcp'

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