cortex-mcp
Un servidor MCP autoalojado que les da la misma memoria a todos los asistentes de IA que uso: una sola carpeta de notas Markdown en mi propio servidor. Claude Code en la computadora y Meta AI en el teléfono la leen y la escriben todos los días, y puedo cambiar de asistente sin tocar las notas. Lo diseñé, lo construí en Go escribiendo primero las pruebas y lo opero en un VPS pequeño.
Arquitectura
Los asistentes se conectan por HTTPS a través de un proxy inverso TLS. Aplicaciones como claude.ai, ChatGPT y Meta AI inician sesión con OAuth 2.1, y los agentes de línea de comandos usan un token Bearer. Dentro del binario, cada petición pasa por la capa HTTP (sesiones, límites de tasa, verificación de tokens) hasta doce herramientas MCP pequeñas, y un solo paquete, vault, toca el disco. El estado de inicio de sesión vive en una base SQLite fuera de la bóveda. Sincronizar las notas con otras máquinas queda en manos de lo que el dueño ya usa, en mi caso Syncthing.
Decisiones clave
Go, por un solo binario pequeño que puedo revisar
Comparé TypeScript, Python, Go y Rust. Go me dio un solo binario estático, una imagen distroless diminuta, una lista corta de dependencias y builds para ARM sin complicaciones. La velocidad de Rust no aporta nada a un servidor que sobre todo lee y escribe archivos pequeños, y era el más difícil de revisar de los cuatro.
La IA puede editar notas y nada más
Las herramientas no tienen shell, ni git, ni acceso a la red. Una nota borrada va a una carpeta .trash en vez de desaparecer. Cada edición debe llevar la versión de la nota que leyó, así que una edición desactualizada de un asistente falla en vez de sobrescribir lo que otro acaba de escribir, y cada escritura queda registrada con el nombre de la app que la hizo.
Tres formas de entrar, guardadas en tres lugares
El dueño inicia sesión con una passkey, un código TOTP o un código de recuperación de un solo uso, cada uno guardado en un lugar distinto, así que perder un teléfono o un gestor de contraseñas nunca me deja fuera. Nada en la web puede crear ni restablecer al dueño: eso requiere un comando en el propio servidor.
La parte difícil
El endpoint de inicio de sesión que podía llenar el disco
Cada inicio de sesión OAuth empieza con una petición a /authorize, y el servidor la guarda hasta que yo la apruebo. La revisión de seguridad del trabajo de OAuth encontró que nada limitaba esas peticiones pendientes, así que cualquiera podía seguir enviándolas hasta llenar el disco. Un solo límite de tasa global tampoco bastaba, porque unas pocas direcciones podían agotarlo y dejarme fuera de mi propio servidor. Hicieron falta tres rondas de revisión para cerrarlo todo: ahora las peticiones pendientes tienen tope por origen y en total, cada origen gasta su propio presupuesto antes que el global, y el inicio de sesión con passkey no toca el presupuesto compartido de códigos, porque una passkey no se puede adivinar.
Seguridad y rendimiento
- Todo acceso a archivos pasa por os.Root de Go y se queda dentro de la bóveda. Los enlaces simbólicos nunca se siguen, y .git, .obsidian y la carpeta del propio servidor están vetados.
- OAuth sigue la especificación de autorización de MCP: PKCE S256, tokens atados a la URL /mcp del servidor, tokens de refresco rotativos con detección de reutilización y URI de redirección limitadas a una lista permitida.
- Los códigos de recuperación, los tokens de refresco y las cookies se guardan solo como hashes, y las escrituras se detienen cuando quedan menos de 1 GiB libres en el disco.
- Cada push corre las pruebas con el detector de carreras, 11 objetivos de fuzzing, staticcheck, govulncheck y gosec. Los releases se firman sin llave con cosign y se publican con SBOM.