Construye un Sistema de Conocimiento Persistente para IA con MCP y PostgreSQL
Cómo construí Neural, un servidor MCP que da memoria persistente a Claude Code usando PostgreSQL, pgvector y embeddings locales con Ollama. Arquitectura, decisiones de diseño y lecciones aprendidas tras 900+ memories en producción.
Los asistentes de IA tienen un problema fundamental: no recuerdan nada entre conversaciones. Cada sesión empieza de cero. Después de meses trabajando con Claude Code y repitiendo las mismas explicaciones sobre mis proyectos, decidí resolver el problema construyendo Neural — un servidor MCP que conecta Claude Code con PostgreSQL y pgvector para darle memoria persistente, búsqueda semántica y contexto de proyecto.
Hoy gestiona 913 memories activas de 15 proyectos, con 60 skills que automatizan flujos de trabajo completos. En este artículo explico la arquitectura, las decisiones de diseño, y cómo puedes construir algo similar.
El Problema: IA Sin Memoria
Si usas un asistente de IA para programar, conoces la frustración:
- Repites contexto: “Este proyecto usa Vue 3 con Pinia, el backend es Express con Prisma…”
- Pierdes decisiones: “Ya decidimos no usar mocks en los tests de integración, ¿recuerdas? No, claro que no.”
- No aprende de errores: El mismo gotcha te muerde en cada sesión
- Sin continuidad: Cada conversación es una isla
El archivo CLAUDE.md ayuda, pero tiene límites: es texto plano, no escala, y no permite búsqueda semántica. Necesitaba algo que creciera con mis proyectos.
¿Qué es MCP?
Model Context Protocol (MCP) es un estándar abierto creado por Anthropic que permite a los LLMs conectarse con herramientas externas. En lugar de que la IA solo lea y escriba archivos, MCP le da acceso a APIs, bases de datos, servicios web — cualquier cosa que expongas como un servidor MCP.
Un servidor MCP expone tools (funciones que la IA puede llamar) y resources (datos que la IA puede leer). Claude Code soporta MCP nativamente, así que cualquier servidor que implementes aparece como herramientas disponibles en tu sesión.
La Arquitectura de Neural
Stack Tecnológico
| Componente | Tecnología | Por qué |
|---|---|---|
| Servidor MCP | TypeScript + @modelcontextprotocol/sdk | SDK oficial, tipado fuerte |
| Base de datos | PostgreSQL 17 | Robusta, extensible, JSONB nativo |
| Embeddings | pgvector + Ollama | Búsqueda semántica sin dependencias cloud |
| Almacenamiento | MinIO (S3-compatible) | Planes, adjuntos y backups |
| Modelo de embeddings | nomic-embed-text (Ollama) | Local, rápido, 768 dimensiones |
Estructura Modular
src/
├── cli.ts # Entrypoint: install/uninstall/run
├── index.ts # McpServer setup + instructions
├── db.ts # Pool PostgreSQL + helpers
├── embedding.ts # Generación de embeddings via Ollama
└── tools/
├── memories.ts # CRUD + búsqueda semántica
├── projects.ts # Registro de proyectos
├── secrets.ts # Gestión de credenciales
├── plans.ts # Planes en S3
└── ... # clients, proposals, devices, etc.
Cada módulo de tools se registra independientemente. Añadir un nuevo dominio es crear un archivo en tools/ y registrarlo en index.ts.
El Modelo de Datos
Memories: El Corazón del Sistema
La tabla memories es donde vive el conocimiento:
CREATE TABLE memories (
id SERIAL PRIMARY KEY,
type TEXT NOT NULL, -- decision, pattern, gotcha, context
project TEXT, -- NULL = global (aplica a todos)
content TEXT NOT NULL,
tags TEXT[],
embedding VECTOR(768), -- pgvector para búsqueda semántica
valid_until TIMESTAMPTZ, -- expiración opcional
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
Cuatro tipos de memoria, cada uno con un propósito claro:
| Tipo | Propósito | Ejemplo |
|---|---|---|
| decision | Decisiones de arquitectura y diseño | ”Usamos repository pattern con inyección de dependencias” |
| pattern | Patrones recurrentes y convenciones | ”Los tests de integración usan PostgreSQL real, nunca mocks” |
| gotcha | Trampas y errores conocidos | ”pg_dump v15 contra PostgreSQL v17 genera backups vacíos” |
| context | Información contextual temporal | ”El cliente necesita la demo lista para el 15 de abril” |
Búsqueda Semántica con pgvector
Cada memory genera un embedding automáticamente al crearse. Esto permite buscar por significado, no solo por texto:
-- Búsqueda semántica: "cómo manejar autenticación"
-- encuentra memories sobre OAuth, JWT, OIDC, passkeys...
SELECT *, 1 - (embedding <=> $1) AS similarity
FROM memories
WHERE project = $2 OR project IS NULL
ORDER BY embedding <=> $1
LIMIT 10;
La combinación de project = $2 OR project IS NULL es clave: las memories globales (sin proyecto) aplican a todos, las específicas solo a su proyecto. Esto permite tener patterns universales (“siempre usar conventional commits”) y específicos (“en este proyecto el deploy es via tags”).
Proyectos como Hub de Integración
CREATE TABLE projects (
name TEXT PRIMARY KEY,
content TEXT,
repo_url TEXT,
notion JSONB, -- { kb_id, adrs_id, bugs_id }
services JSONB, -- { plane: { project_id, identifier } }
tags TEXT[],
embedding VECTOR(768)
);
Cada proyecto enlaza Neural con servicios externos: Notion para documentación, Plane para gestión de tareas, GitLab para código. Cuando la IA necesita crear un work item, consulta project_get para saber qué tracker usar y con qué configuración.
Embeddings Locales con Ollama
Una decisión importante fue generar embeddings localmente con Ollama en lugar de usar APIs cloud:
// embedding.ts (simplificado)
async function generateEmbedding(text: string): Promise<number[]> {
const response = await fetch(`${OLLAMA_URL}/api/embeddings`, {
method: 'POST',
body: JSON.stringify({
model: 'nomic-embed-text',
prompt: text.substring(0, 8192) // truncar a 8K chars
})
});
return response.json().then(r => r.embedding);
}
Ventajas de embeddings locales:
- Sin costes por llamada — genera tantos embeddings como necesites
- Sin dependencias externas — funciona offline
- Privacidad — tu código y decisiones nunca salen de tu red
- Velocidad — Ollama en un Mac Mini M2 genera embeddings en ~50ms
Trade-off: El modelo local (nomic-embed-text, 768d) es menos preciso que text-embedding-3-large de OpenAI (3072d). En la práctica, para buscar dentro de 900 memories la diferencia es imperceptible.
El Sistema de Skills
Las tools del MCP dan acceso a los datos. Los skills son prompts estructurados que definen flujos de trabajo completos. Se instalan como archivos en ~/.claude/skills/ y Claude Code los detecta automáticamente.
Ejemplo: neural-do (Tomar una tarea)
Cuando digo /neural-do NEURAL-42, el skill:
- Consulta
project_getpara obtener el tracker del proyecto - Carga el work item de Plane con su descripción y estado
- Busca memories relacionadas (decisions, patterns, gotchas)
- Carga el prompt del módulo Plane si el item tiene uno asignado
- Presenta el contexto completo y transiciona a
/neural-plano/neural-debug
Ejemplo: neural-capture (Guardar conocimiento)
Al final de una sesión productiva, /neural-capture:
- Revisa qué se hizo en la sesión (commits, archivos modificados)
- Propone memories para guardar (decisions, patterns, gotchas detectados)
- Antes de guardar, hace búsqueda semántica cross-project para detectar duplicados
- Si encuentra similarity >= 0.80, sugiere globalizar en vez de crear una específica
- Genera embeddings y persiste
Los 60 Skills
Se organizan en dos familias:
22 neural-* (workflow): brainstorm, plan, execute, tdd, debug, do, finish, worktree, release, check, capture, init, status, load-patterns, refactor, document, sync-notion, write-skill, help, review, cycle-close, audit.
38 stack-* (expertos tecnológicos): typescript, vue, prisma, postgresql, docker, git, y muchos más. Cada uno carga patterns específicos de su tecnología desde Neural antes de responder.
Integraciones
Neural no vive aislado. Se conecta con el ecosistema de herramientas:
Plane (Gestión de Tareas)
project_get("nexus") → services.plane.project_id
→ mcp__plane__list_work_items(project_id)
→ mcp__plane__create_work_item(...)
Los skills detectan automáticamente si un proyecto usa Plane, GitLab Issues, o ningún tracker, y adaptan su comportamiento.
Notion (Documentación)
Sincronización on-demand de memories a Knowledge Base de Notion. Neural es la fuente de verdad (SSoT); Notion es la capa de presentación para documentación editorial.
MinIO (Almacenamiento S3)
- Planes de implementación:
neural/plans/{project}/{date}-{slug}.md - Adjuntos:
neural/attachments/{memory_id}/{filename} - Backups:
neural/backups/{YYYY-MM-DD}.sql.gz— pg_dump diario automatizado
Lecciones Aprendidas
1. Los Gotchas Son Oro
De los 4 tipos de memoria, los gotchas son los más valiosos en la práctica. Representan el 38% de todas las memories (346 de 913). Son errores que ya cometiste y documentaste — la IA los encuentra antes de que vuelvas a caer en la misma trampa.
2. Global vs. Específico
Las memories sin proyecto (project IS NULL) aplican a todos los proyectos. Esto es poderoso: un pattern como “siempre usar conventional commits en español” se escribe una vez y aplica en los 15 proyectos. La tentación es hacer todo global, pero la regla es: global solo si aplica universalmente.
3. Los Embeddings No Son Magia
La búsqueda semántica es potente, pero no reemplaza al texto. Una query de “autenticación” encontrará memories sobre OAuth, JWT y passkeys, pero no encontrará una gotcha sobre “el header X-Forwarded-For se pierde en el reverse proxy” aunque sea crítica para auth. Por eso Neural ofrece ambas: memory_search (texto/tags) y memory_semantic_search (embeddings).
4. La Memoria Necesita Mantenimiento
Las memories se vuelven obsoletas. Un skill como /neural-audit revisa periódicamente: memories sin embedding, duplicados semánticos (similarity > 0.9), memories expiradas, y patterns que ya no coinciden con el código actual. Sin esta higiene, la base de conocimiento se degrada.
5. Las Instructions del MCP Son el Contexto Más Barato
El campo instructions del servidor MCP se inyecta en cada conversación. Es el lugar perfecto para reglas que aplican siempre: “Neural es SSoT”, “consultar project_get antes de interactuar con Plane”, “las queries de memoria se pueden ejecutar en paralelo”. Cero tokens extra por consulta.
Cómo Empezar Tu Propio Sistema
No necesitas replicar Neural al completo. Empieza con lo mínimo:
Paso 1: PostgreSQL + pgvector
CREATE EXTENSION vector;
CREATE TABLE memories (
id SERIAL PRIMARY KEY,
type TEXT NOT NULL CHECK (type IN ('decision', 'pattern', 'gotcha', 'context')),
project TEXT,
content TEXT NOT NULL,
tags TEXT[] DEFAULT '{}',
embedding VECTOR(768),
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX ON memories USING ivfflat (embedding vector_cosine_ops);
Paso 2: MCP Server Mínimo
import { McpServer } from "@modelcontextprotocol/sdk/server/stdio.js";
import pg from "pg";
const server = new McpServer({ name: "my-memory", version: "1.0.0" });
const pool = new pg.Pool({ connectionString: process.env.DSN });
server.tool("memory_create", { type: "string", project: "string", content: "string" },
async ({ type, project, content }) => {
const embedding = await generateEmbedding(content);
await pool.query(
"INSERT INTO memories (type, project, content, embedding) VALUES ($1, $2, $3, $4)",
[type, project, content, JSON.stringify(embedding)]
);
return { content: [{ type: "text", text: "Memory saved" }] };
}
);
server.tool("memory_search", { query: "string", project: "string" },
async ({ query, project }) => {
const embedding = await generateEmbedding(query);
const result = await pool.query(
`SELECT *, 1 - (embedding <=> $1) AS similarity
FROM memories WHERE (project = $2 OR project IS NULL)
ORDER BY embedding <=> $1 LIMIT 10`,
[JSON.stringify(embedding), project]
);
return { content: [{ type: "text", text: JSON.stringify(result.rows) }] };
}
);
Paso 3: Registrar en Claude Code
// ~/.claude.json
{
"mcpServers": {
"my-memory": {
"command": "node",
"args": ["/path/to/dist/index.js", "--dsn", "postgresql://..."]
}
}
}
Con esto ya tienes un sistema funcional. La IA puede guardar y buscar conocimiento entre sesiones. A partir de aquí, iteras: añades proyectos, tags, skills, integraciones.
Números en Producción
Después de ~3 meses de uso diario con Neural:
| Métrica | Valor |
|---|---|
| Memories activas | 913 |
| Memories archivadas | 74 |
| Proyectos registrados | 15 |
| Skills instalados | 60 |
| Cobertura de embeddings | 99.9% (912/913) |
| Tipos: gotcha / pattern / decision / context | 346 / 264 / 207 / 96 |
| Tablas adicionales | clients, proposals, devices, finances, secrets |
Conclusión
Darle memoria persistente a una IA cambia fundamentalmente la relación de trabajo. Pasa de ser un asistente genérico que necesita contexto constante a ser un colaborador que conoce tus proyectos, recuerda tus decisiones, y aprende de los errores pasados.
MCP hace esto posible de forma estándar y extensible. PostgreSQL + pgvector dan la base robusta que necesitas. Y Ollama mantiene todo local y privado.
Neural empezó como un experimento para no repetirme. Tres meses después, es la pieza central de mi flujo de desarrollo. Si trabajas con IA a diario, construir tu propio sistema de memoria es probablemente la mejor inversión de tiempo que puedes hacer.
¿Tienes preguntas sobre la implementación o quieres compartir tu enfoque para dar memoria a la IA? Únete al grupo de Telegram para comentar.