Athena MCP
Athena
Una wiki personal a la que tu IA escribe y tú puedes navegar.
Athena coloca un servidor MCP delante de Wiki.js. Tu asistente busca en la wiki, lee páginas y guarda otras nuevas: notas, documentación, conversaciones enteras. Todo lo que escribe es una página Markdown normal que puedes abrir, editar y conservar mucho después de que cualquier modelo concreto haya desaparecido.
Claude / ChatGPT / Cursor
│ MCP over HTTPS
▼
athena-mcp ──── search ──▶ Wiki.js (keyword) + Postgres (meaning)
│ read ────▶ Wiki.js
└──────── write ───▶ Wiki.js ──▶ athena-indexer ──▶ PostgresWiki.js contiene la verdad. El índice vectorial solo ayuda a encontrar cosas, y se puede eliminar y reconstruir en cualquier momento.
Ponlo en marcha | |
Úsalo | |
Ejecútalo de verdad | |
Referencia |
Inicio rápido
Local, en unos cinco minutos. Para cualquier cosa en internet, lee primero Despliega en un servidor.
git clone https://github.com/jannismilz/athena.git
cd athena
cp .env.example .env
$EDITOR .env # fill in every CHANGE_ME, one per secret:
# openssl rand -hex 32
docker compose up -dLuego:
Abre Wiki.js y completa el asistente de configuración.
En Wiki.js: Administración → API, actívalo, crea un token e introdúcelo en
.envcomoWIKI_API_TOKEN.docker compose up -dde nuevo para que lo recoja.Abre el panel de control e inicia sesión con
DASHBOARD_TOKEN.
Los datos se escriben en un directorio data/ junto a la copia del repositorio, no dentro de ella,
por lo que ninguna operación de git puede eliminarlos nunca. Cambia ATHENA_DATA_DIR si quieres que esté
en otro lugar.
Nada publica un puerto, así que accede a los servicios a través de tu proxy inverso, o
añade un mapeo temporal de ports: mientras lo pruebas.
La primera descarga descarga un modelo de incrustación de unos pocos cientos de MB. El indexador
reintenta hasta que esté listo, por lo que es normal que embeddings aparezca como no saludable durante uno o dos minutos
en el primer arranque.
Related MCP server: wiki-js-mcp
Conecta tu IA
Todo se sirve desde MCP_PUBLIC_URL, que debe ser un origen https:// simple
sin ruta. No /mcp.
Claude.ai → Configuración → Conectores → Añadir conector personalizado
URL:
https://athena-mcp.example.com/mcpDeja el ID de cliente y el secreto vacíos. Athena registra el cliente por sí mismo.
Una página del navegador pide una contraseña. Es tu
MCP_TOKEN.
Cursor, Claude Desktop y otros clientes de cabecera
{
"mcpServers": {
"athena": {
"url": "https://athena-mcp.example.com/mcp",
"headers": { "Authorization": "Bearer YOUR_MCP_TOKEN" }
}
}
}Herramientas
Herramienta | Qué hace |
| Búsqueda por palabras clave y semántica, fusionada. Cada resultado lleva una ruta. |
| Markdown completo de una página |
| Esquema de encabezados, sin el cuerpo |
| Añade bajo un encabezado, dejando el resto intacto |
| Nueva página Markdown |
| Reemplaza el cuerpo de una página |
| Mueve o renombra |
| Elimina y lo borra del índice |
| Archiva una conversación en |
| Nota rápida en |
| Todo, con rutas y marcas de tiempo |
| Tamaño, forma y desactualización, para que la IA responda qué falta |
append_to_page es la que merece la pena conocer: añadir un hecho cuesta un
párrafo, no una reescritura de toda la página.
Por qué recupera bien. Los términos exactos alcanzan el índice de texto completo de Wiki.js, las preguntas vagas alcanzan el índice vectorial, y los resultados se fusionan con fusión de rango recíproco para que ninguna fuente pueda ocultar a la otra. Los fragmentos registran los encabezados que tienen encima, por lo que lo que se devuelve conserva su contexto. Cada página que un asistente toca se marca con cuál fue y cuándo, tomado del cliente autenticado en lugar de lo que el modelo afirma sobre sí mismo.
Panel de control
Su propio servicio, en el puerto 8082. Inicia sesión con DASHBOARD_TOKEN; no hay ningún token
en ninguna URL. Para scripts, usa una cabecera de portador:
curl -H "Authorization: Bearer $DASHBOARD_TOKEN" \
https://wiki.example.com/dashboard/api/metrics?days=30Panel | Responde |
Contenido | páginas, palabras, por área, más grandes, quedándose obsoletas |
Actividad de la IA | llamadas por día, qué herramientas, qué asistente, lectura vs escritura |
Búsquedas que no encontraron nada | lo que tu wiki no pudo responder |
Salud del índice | fragmentos almacenados, páginas indexadas, cuánto retraso |
Copia de seguridad | cuándo terminó la última ejecución, tamaño, adónde fue |
La tercera fila es la que se gana su lugar. Cada entrada es una página que merece la pena escribir.
Es de solo lectura por partida doble: nunca escribe y se conecta a Postgres como
athena_readonly, un rol que solo tiene SELECT. Las cifras se
agregan en Postgres y se almacenan en caché, por lo que una actualización casi no cuesta nada.
Despliega en un servidor
Un VPS de 4 GB ejecuta todo, incluido el modelo de incrustación en CPU.
1. Alojamiento y cortafuegos
sudo ufw default deny incoming && sudo ufw default allow outgoing
sudo ufw allow 22/tcp && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
sudo ufw enableInstala Docker, luego crea un usuario que sea propietario del despliegue:
sudo useradd --create-home --shell /bin/bash athena
sudo usermod -aG docker athenaEjecuta compose como ese usuario, nunca con sudo, o los montajes bind acaban siendo propiedad de
root. La pertenencia al grupo docker equivale a ser root en el host, así que
mantenlo pequeño.
2. DNS
Dos registros A que apuntan al host:
Nombre | Sirve |
| Wiki.js y el panel de control bajo |
| el endpoint MCP |
3. Organízalo y configúralo
Todo lo que Athena escribe pasa por una configuración, ATHENA_DATA_DIR, por lo que
toda la instalación puede vivir bajo un solo directorio. Usa dos subdirectorios
con diferentes ciclos de vida:
/athena
├── app/ the git repository replaceable, thrown away on every upgrade
└── data/ postgres, state, irreplaceable, never touched by git
uploads, backupsSon hermanos, no anidados, y ese es el objetivo. data/ está en
.gitignore, y git clean -xdf elimina los archivos ignorados, por lo que los datos dentro de la
copia del repositorio están a una orden rutinaria de ser borrados sin confirmación y sin
deshacer. Un directorio hermano no puede ser alcanzado por ninguna operación de git.
El valor predeterminado ATHENA_DATA_DIR=../data te da esta disposición automáticamente, por lo que
no hay nada que recordar.
sudo mkdir -p /athena && sudo chown athena:athena /athena
cd /athena
git clone https://github.com/jannismilz/athena.git app
cd app
cp .env.example .env
chmod 600 .env # it holds every secretATHENA_DATA_DIR por defecto es ../data, que se resuelve con respecto al directorio
que contiene el archivo compose. Clona en /athena/app como arriba y los datos caen en
/athena/data sin nada que configurar. Establece los secretos:
POSTGRES_PASSWORD=...
MCP_TOKEN=...
DASHBOARD_TOKEN=...
DASHBOARD_DB_PASSWORD=...
MCP_PUBLIC_URL=https://athena-mcp.example.com
WIKI_PUBLIC_URL=https://wiki.example.comCompose crea /athena/data y sus subdirectorios en el primer inicio. Ejecuta cada
comando docker compose desde /athena/app.
/athena/data
├── postgres/ the wiki, users, settings, uploads, activity log, vectors
├── wikijs/ Wiki.js config, cache, upload cache
├── mcp/ oauth-state.json, the tokens issued to AI clients
├── indexer/ index bookkeeping, rebuilt automatically if lost
├── embeddings/ the downloaded model
└── backups/ local dumps plus status.jsonSolo postgres/ es irremplazable, y el contenedor de copia de seguridad lo vuelca cada hora.
Todo lo demás se regenera automáticamente o cuesta una reconexión.
Si prefieres seguir la convención de jerarquía del sistema de archivos, coloca los datos en
/srv/athena y la copia del repositorio en /opt/athena. La disposición de raíz única
anterior es más simple en una máquina que hace un trabajo, y cualquiera de las dos funciona: solo
ATHENA_DATA_DIR decide.
4. Proxy inverso
Ningún contenedor publica un puerto. Los servicios se sientan en dos redes:
athena, interna. Postgres, el modelo de incrustación y el indexador viven aquí solo, por lo que un proxy comprometido no puede alcanzar la base de datos.athena-edge, a la que se une tu proxy inverso. Solo los tres servicios siguientes están en ella.
Enruta estos:
Host | A | Notas |
|
| Actualización WebSocket, límite de cuerpo de 100M |
|
| |
|
| no debe almacenar en búfer, flujos MCP |
Reenvía X-Forwarded-For: los inicios de sesión limitan por dirección, y sin él cada
intento parece venir del proxy.
Ejecuta nginx como un contenedor unido a la red athena-edge, como se muestra a continuación, o en el
host con un mapeo ports: vinculado a 127.0.0.1. Unirse a la red de borde
significa que el proxy puede alcanzar Wiki.js, el servidor MCP y el panel de control, y
nada más.
server {
listen 80;
server_name wiki.example.com;
location / {
proxy_pass http://wikijs:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
client_max_body_size 100M;
proxy_read_timeout 120s;
}
location /dashboard/ {
proxy_pass http://dashboard:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name athena-mcp.example.com;
location / {
proxy_pass http://mcp:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# MCP streams responses. Without these, long tool calls appear to hang.
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
}
}Luego emite certificados con certbot, o termina TLS donde ya lo hagas.
Nada aquí es específico de una plataforma. Un PaaS que ejecuta Compose y proporciona
su propio proxy necesita tres configuraciones, todas en .env:
ATHENA_DATA_DIR=../files # Dokploy's persistent directory
ATHENA_EDGE_NETWORK=dokploy-network
ATHENA_EDGE_EXTERNAL=trueATHENA_DATA_DIR es lo más importante: Dokploy limpia las rutas de montaje bind absolutas en
el redespliegue, por lo que una ruta absoluta allí destruiría la base de datos. Una ruta
relativa al directorio de la aplicación sobrevive.
Luego añade dominios en la interfaz de usuario de la plataforma, apuntando al servicio y su puerto:
Dominio | Servicio | Puerto |
|
| 3000 |
|
| 8080 |
|
| 8082 |
La plataforma genera sus propias etiquetas de enrutamiento y maneja TLS, así que omite la sección de nginx por completo. Todo lo demás, incluido el archivo compose, no cambia.
No necesitas publicar imágenes en un registro: Dokploy construye desde el repositorio. Construir cuatro imágenes compite con Postgres y el modelo de incrustación por la memoria, por lo que en un host pequeño puede ser preferible construir en CI y extraer en su lugar.
5. Inicia, luego bloquea la wiki
docker compose up -d && docker compose psCompleta el asistente de Wiki.js inmediatamente. Hasta que lo hagas, cualquiera que encuentre el host puede reclamar la cuenta de administrador. Luego, en Wiki.js:
Grupos → Invitados: elimina el acceso de lectura, a menos que quieras la wiki pública.
Autenticación: desactiva el auto-registro.
API: actívala y crea el token para
WIKI_API_TOKEN.
6. Verifica
curl -s https://athena-mcp.example.com/health
# Must reject unauthenticated calls:
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://athena-mcp.example.com/mcp
# expected: 401Copias de seguridad
Un pg_dump es una copia de seguridad completa. Wiki.js guarda páginas, historial, usuarios,
permisos, configuraciones y los bytes de cada archivo subido en Postgres.
Las subidas viven en la tabla assetData; los archivos bajo data/wikijs/uploads son
solo una caché. El registro de actividad de Athena y los vectores de búsqueda están en una segunda base de datos
en el mismo servidor.
Datos | En la copia de seguridad |
Páginas, historial, usuarios, configuraciones | sí |
Imágenes y archivos subidos | sí |
Registro de actividad y vectores de búsqueda | sí |
Contabilidad del índice, registros OAuth | no, se reconstruye o reconecta |
| no, guarda una copia en un gestor de contraseñas |
El contenedor backup se ejecuta cada hora. Cada ejecución vuelca ambas bases de datos, comprueba
que cada volcado sea legible, mantiene una copia local, lo envía a tu destino de rclone,
verifica que la subida coincida, y solo entonces poda. Una ejecución fallida nunca puede eliminar
tu última copia de seguridad buena.
docker compose run --rm backup now # take one now
docker compose run --rm backup restore list # see what exists
docker compose logs -f backup # watch the scheduleConfigúralo completamente en .env. Cualquier destino de rclone funciona: S3, Backblaze, Wasabi, MinIO, Hetzner. Deja BACKUP_REMOTE vacío para mantener las copias de seguridad solo en el host.
Agrega un remote crypt y apunta BACKUP_REMOTE hacia él. El destino entonces solo recibe texto cifrado, incluidos los nombres de archivo.
BACKUP_REMOTE=crypt:
RCLONE_CONFIG_CRYPT_TYPE=crypt
RCLONE_CONFIG_CRYPT_REMOTE=s3:my-bucket/athena
RCLONE_CONFIG_CRYPT_PASSWORD=<rclone obscure ...>
RCLONE_CONFIG_CRYPT_PASSWORD2=<rclone obscure ...>Mantén ambas contraseñas en tu gestor de contraseñas. Sin ellas, las copias de seguridad son ilegibles, incluso para ti.
Restauración
Practica esto antes de necesitarlo. Una restauración que nadie ha ejecutado es una suposición.
docker compose run --rm backup restore list
docker compose stop wikijs mcp indexer dashboard
docker compose run --rm backup restore run 2026-08-18T115529Z
docker compose start wikijs mcp indexer dashboardTe pide que escribas el nombre de la base de datos para confirmar. restore fetch <stamp> descarga una copia sin restaurarla, e informa si cada volcado es legible.
El índice de búsqueda se repara solo después: el indexador vuelve a leer cada página y re-embedido todo aquello cuyo contenido cambió.
Configuración
Todo proviene del entorno. Cada servicio valida su propia configuración al arrancar y sale con una lista de lo que está mal, así que un error tipográfico falla de inmediato en lugar de a las tres de la mañana.
Los cinco secretos, todos generados por ti. Ninguna credencial perteneciente a Claude, OpenAI o cualquier otro se almacena nunca en .env.
Secreto | Mantenido por | Protege |
| postgres, mcp, indexer | acceso completo a la base de datos |
| mcp, indexer | la API de Wiki.js |
| mcp | el endpoint MCP |
| dashboard | el inicio de sesión del panel |
| dashboard, mcp, indexer | un rol de base de datos de solo SELECT |
Qué se ejecuta
Servicio | Puerto | Qué es |
| interno | Datos de Wiki.js, registro de actividad y vectores mediante pgvector |
| 3000 | El wiki que lees y editas |
| interno | El modelo de embeddings, en CPU |
| 8080 | A lo que se conecta tu IA |
| 8081 | Mantiene el índice de vectores sincronizado con el wiki |
| 8082 | Métricas |
| ninguno | Volcado por hora, verificación, envío |
No hay una base de datos de vectores separada. Los vectores viven en Postgres, por lo que una sola copia de seguridad cubre todo.
En hosts ARM la imagen de embeddings se publica solo para
linux/amd64y no se ejecutará de forma nativa. ApuntaEMBEDDINGS_PROVIDER=openaia un endpoint compatible con OpenAI como Ollama.
Variable | Predeterminado | Notas |
|
| Raíz de todos los bind mounts, un hermano del checkout |
|
| Se muestra en la página de inicio de sesión y el panel |
|
| Red a la que se une tu proxy inverso |
|
|
|
|
|
|
|
| Marcas de procedencia y rutas con fecha |
|
| La base de datos de Wiki.js |
|
| Registro de actividad y vectores, creada automáticamente |
|
| Idioma del contenido |
|
| Usado para enlaces del panel |
| requerido | Origen https desnudo, sin ruta |
|
| Cuánto tiempo se reutilizan las cifras del panel |
|
| Cambiarlo re-indexa todo |
|
|
|
|
| Intervalo de reconciliación completa |
|
| Límite máximo de tamaño de fragmento |
| ver | Programación, retención, destino de rclone |
Cambiar EMBEDDINGS_MODEL cambia el ancho del vector, y los vectores de dos modelos no se pueden comparar, por lo que el indexador reconstruye la tabla y re-embedido cada página. El contenido de Wiki.js no se modifica.
Seguridad
Cada contenedor recibe solo las credenciales que utiliza. El panel no recibe ni POSTGRES_PASSWORD ni WIKI_API_TOKEN, por lo que comprometerlo otorga acceso de lectura y nada más. Verifica en cualquier momento:
docker compose exec dashboard env | grep -iE 'PASSWORD|TOKEN'Las solicitudes MCP no autenticadas reciben 401 y ninguna explicación.
Ambos caminos de inicio de sesión limitan después de 5 fallos por dirección; un enlace de inicio de sesión se quema después de 3 intentos.
Las sesiones del panel son cookies firmadas que llevan una caducidad y un nonce, nunca el token.
HttpOnly,SameSite=Strict, y se rechazan las publicaciones entre sitios.Las comparaciones de secretos son de tiempo constante.
Los encabezados de proxy solo se confían desde loopback, por lo que un cliente remoto no puede falsificar su dirección para evitar una limitación.
Los contenedores se ejecutan como un usuario no root.
Deliberadamente ausente: permisos por herramienta. Cualquier cliente autenticado puede llamar a cualquier herramienta, incluyendo delete_page. Wiki.js mantiene el historial de páginas, por lo que un borrado es recuperable, pero trata MCP_TOKEN como acceso completo de escritura a tu wiki. Athena también asume un único propietario; Wiki.js tiene sus propios usuarios para leer el wiki.
MCP_TOKEN funciona de dos maneras, porque los clientes de IA se autentican de dos maneras.
Clientes de encabezado como Cursor y Claude Desktop envían Authorization: Bearer <MCP_TOKEN>. Ese es el mecanismo completo.
Claude.ai en el navegador no puede hacer eso. Sus conectores personalizados solo soportan OAuth, y la especificación MCP requiere registro dinámico de clientes, por lo que un servidor que acepte Claude en el navegador tiene que ser un servidor de autorización. Athena implementa uno:
Claude se registra y recibe un id de cliente generado. No interviene ningún secreto tuyo.
Claude te envía a una página de inicio de sesión en tu propio servidor.
Escribes
MCP_TOKENcomo contraseña. Ese es el paso de aprobación humana.Athena emite tokens de Claude que Athena misma ha acuñado.
Esos tokens se escriben en data/mcp/oauth-state.json, nunca en .env. Revócalos con:
rm data/mcp/oauth-state.json && docker compose restart mcpSi nunca usas Claude en el navegador, ignora todo esto. La ruta del bearer no lo toca.
Operaciones
docker compose logs -f mcp
curl -s localhost:8081/stats | python3 -m json.tool
# Force a full reconciliation
docker compose exec -T indexer bun -e 'await fetch("http://127.0.0.1:8081/sync",{method:"POST"})'Actualización. Siempre haz una copia de seguridad primero: Wiki.js ejecuta sus propias migraciones al iniciar, y esas no son reversibles deteniendo el contenedor.
docker compose run --rm backup now
git pull && docker compose build && docker compose up -dSíntoma | Causa |
Un servicio sale al arrancar listando configuración | Falta una variable requerida o sigue siendo |
Claude no puede conectarse, no hay página de inicio |
|
El inicio de sesión rechaza la contraseña correcta | Limitado después de 5 fallos, espera un minuto |
No hay resultados de búsqueda semántica |
|
El panel muestra páginas atrasadas | El indexador se está poniendo al día, revisa sus registros |
Las llamadas a herramientas fallan con 401 | El archivo de estado se borró o el token cambió, reconecta el cliente |
Postgres sale, "los archivos de base de datos son incompatibles" | La versión mayor de la imagen cambió bajo datos existentes |
Postgres no leerá un directorio de datos escrito por una versión mayor diferente. Volcar, limpiar, restaurar:
docker compose run --rm backup now # on the OLD version
docker compose down
mv data/postgres data/postgres.old # keep until you are happy
# edit the image tag in docker-compose.yml and the FROM line in
# docker/backup/Dockerfile to the same new major version
docker compose build backup
docker compose up -d postgres
docker compose run --rm backup restore run <stamp> # once per database
docker compose up -dEl índice de vectores se restaura con todo lo demás, por lo que no se re-embedido nada.
Desarrollo
bun install
bun test # 145 tests
bun run check # typecheck, lint, testPaquete | Qué es |
| Cliente de Wiki.js, fragmentación, fusión de búsqueda, vectores, autenticación, configuración |
| Servidor MCP, servidor de autorización OAuth, las herramientas |
| Bucle de sincronización, embeddings, escrituras de vectores, API de búsqueda interna |
| Interfaz de métricas |
| Contenedor de copia de seguridad y restauración |
| El sitio de una página |
| CSS y JS opcionales de Wiki.js |
Bun ejecuta TypeScript directamente, por lo que no hay paso de compilación y los contenedores ejecutan el código fuente. bun run --cwd packages/dashboard preview escribe un preview.html con datos de muestra.
Cómo encaja:
El indexador es incremental. Huella digital de cada página y omite todo lo que no ha cambiado, por lo que un pase sobre un wiki intacto no cuesta nada.
Cada servicio con credenciales de administrador prepara la base de datos al arrancar, bajo un bloqueo de asesoramiento, por lo que el orden de inicio no importa.
El panel es HTML renderizado en el servidor con gráficos SVG en línea. Sin JavaScript del cliente, sin biblioteca de gráficos, sin paso de compilación.
Publicación del sitio web. website/index.html se despliega en GitHub Pages en cada push que lo toque. Habilita Pages una vez manualmente primero: Settings → Pages → Build and deployment → Source: GitHub Actions. Esto no se puede automatizar, porque crear un sitio Pages necesita un token con derechos de administración y GITHUB_TOKEN no los tiene.
Licencia
Apache-2.0. Ver LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.
Hosted markdown project wikis your team's AI assistants read, search, and update over MCP.
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
One memory, every AI. A shared, user-owned markdown memory your AI clients read and write over MCP.
Related MCP Servers
- AlicenseAqualityCmaintenanceAn MCP server that enables AI agents to interact with Wiki.js as a knowledge base through a comprehensive set of 29 tools for content retrieval and management. It supports full-text search, page versioning, and asset browsing with optional write operations secured by safety gates.2928 npm8MIT
- AlicenseAqualityDmaintenanceAn MCP server for Wiki.js that enables AI agents to create, read, update, search, list, and move wiki pages via the GraphQL API. It supports surgical section updates and structured content management through named sections.6MIT
- AlicenseAqualityCmaintenanceAn MCP server that enables AI agents to compile, refine, and interlink knowledge into a persistent wiki, replacing RAG with structured, curated knowledge.1528 npm3MIT
- AlicenseNot gradedqualityCmaintenanceMCP server for Wiki.js integration, enabling AI assistants to create, read, update, delete, search, and move wiki pages via natural language.1MIT