AAS MCP Server
Un adaptador MCP de AAS que expone APIs de Asset Administration Shell configuradas como herramientas del Model Context Protocol, permitiendo que agentes de LLM interactúen con cualquier backend compatible con AAS.
Documentación
AAS MCP Server
Acerca de este proyecto
Puente OpenAPI-to-MCP para APIs de Asset Administration Shell (AAS)
Un adaptador AAS MCP que expone APIs configuradas de Asset Administration Shell como herramientas del Model Context Protocol, permitiendo que agentes LLM interactúen con cualquier backend compatible con AAS.
Requisitos y Configuración
Prerrequisitos
- Especificaciones OpenAPI de AAS - Descargar desde GitHub
- Servidor Backend de AAS - Servidor SAP BNAC AAS, Eclipse BaSyx, Servicio FA³ST, etc.
- Python 3.12+ O Docker
Configuración
-
Obtener las Especificaciones de AAS:
mkdir specs && cd specs # Download from https://github.com/admin-shell-io/aas-specs/tree/main/schemas/openapi -
Crear config.yaml (copiar desde config.yaml.template):
components: aas-repo: official_spec: specs/AssetAdministrationShellRepositoryServiceSpecification-V3.1.1_SSP-001.yaml curation: allowlist: - [get, "*"] # All GET operations (wildcard) - [post, /shells] -
Ejecutar:
# Docker (recommended) — pulls the pre-built image from GitHub Container Registry docker run \ -v $(pwd)/config.yaml:/app/config/config.yaml \ -v $(pwd)/specs:/app/specs \ -e AAS_COMPONENT=aas-repo \ -e AAS_BASE_URL=http://your-backend:8080 \ -i ghcr.io/sap/aas-mcp-server:latest # Or install locally pip install -e . aas-mcp-server --component aas-repo --base-url http://localhost:8080 --config config.yamlLa imagen se publica como multi-arquitectura (
linux/amd64,linux/arm64) enghcr.io/sap/aas-mcp-server. Etiquetas disponibles:latest,<major>.<minor>(p. ej.0.1), y<version>(p. ej.0.1.0). No se requiere inicio de sesión: el paquete es público.
Configuración
Básica (Solo Especificación Oficial)
components:
aas-repo:
official_spec: specs/aas-repo-spec.yaml
Filtrada (Específica de la Implementación)
Filtra solo los endpoints que tu backend soporta:
components:
aas-repo:
official_spec: specs/aas-repo-official.yaml
implementation_spec: specs/aas-supported-endpoints.yaml
Resultado: Solo se exponen los endpoints que están en ambas especificaciones (intersección).
Con Curaduría (Comodines Soportados)
Controla qué operaciones se exponen usando comodines:
components:
aas-repo:
official_spec: specs/aas-repo-spec.yaml
curation:
allowlist:
# Specific operations
- [get, /shells]
- [post, /shells]
# Wildcards
- [get, "*"] # All GET operations on any path
- ["*", /shells] # All methods on /shells path
- ["*", "*"] # All methods on all paths (use with caution!)
aliases:
GetAllAssetAdministrationShells: list_shells
PostAssetAdministrationShell: create_shell
Consulta config.yaml.template para ver todas las opciones.
Configuración del Cliente MCP
El mismo binario aas-mcp-server funciona con todos los clientes compatibles con MCP: la lógica del servidor es idéntica, solo cambia el formato de configuración según el cliente. Ejemplos completos para todos los clientes están disponibles en client_config_examples.txt.
Claude Desktop
Edita ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
o %APPDATA%\Claude\claude_desktop_config.json (Windows).
Consulta claude_desktop_config.example.json para
ver el ejemplo completo de cuatro componentes.
{
"mcpServers": {
"aas-repo": {
"command": "aas-mcp-server",
"args": [
"--component", "aas-repo",
"--base-url", "http://localhost:8080",
"--config", "/path/to/your/config.yaml"
],
"env": { "LOG_LEVEL": "INFO" }
}
}
}
Claude CLI (Claude Code)
claude mcp add aas-repo \
--env LOG_LEVEL=INFO \
-- aas-mcp-server \
--component aas-repo \
--base-url http://localhost:8080 \
--config /path/to/your/config.yaml
Opciones de alcance: --scope local (predeterminado, proyecto actual), --scope user (todos los proyectos),
--scope project (compartido con el equipo vía .mcp.json).
OpenCode
Agrega a opencode.json en la raíz de tu proyecto:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"aas-repo": {
"type": "local",
"command": [
"aas-mcp-server",
"--component", "aas-repo",
"--base-url", "http://localhost:8080",
"--config", "/path/to/your/config.yaml"
],
"enabled": true,
"environment": { "LOG_LEVEL": "INFO" }
}
}
}
Consulta client_config_examples.txt para los cuatro componentes, configuración de autenticación y configuración de modo escritura para cada cliente.
Uso con Docker
La imagen preconstruida se publica en GitHub Container Registry en
ghcr.io/sap/aas-mcp-server (multi-arquitectura linux/amd64 + linux/arm64, con SBOM
y procedencia de compilación atestiguada). Los ejemplos a continuación usan :latest; fija una
versión específica (:0.1.0) o línea menor (:0.1) para despliegues reproducibles.
Básico (stdio — uso local, sin autenticación)
docker run \
-v $(pwd)/config.yaml:/app/config/config.yaml \
-v $(pwd)/specs:/app/specs \
-e AAS_COMPONENT=aas-repo \
-e AAS_BASE_URL=http://your-backend:8080 \
-i ghcr.io/sap/aas-mcp-server:latest
Transporte HTTP con OAuth 2.1
Para despliegues remotos donde los clientes MCP se conectan a través de la red:
docker run \
--network your-docker-network \
-v $(pwd)/config.yaml:/app/config/config.yaml \
-v $(pwd)/specs:/app/specs \
-e AAS_COMPONENT=aas-repo \
-e AAS_BASE_URL=http://your-backend:8080 \
-e MCP_TRANSPORT=streamable-http \
-e MCP_HOST=0.0.0.0 \
-e MCP_PORT=8000 \
-e OAUTH_ISSUER_URL=https://your-idp/realms/your-realm \
-e OAUTH_CLIENT_ID=your-client-id \
-e OAUTH_CLIENT_SECRET=your-client-secret \
-e OAUTH_SERVER_BASE_URL=http://localhost:8000 \
-p 8000:8000 \
ghcr.io/sap/aas-mcp-server:latest
Regístrate con un cliente MCP (ejemplo usando Claude CLI):
claude mcp add aas-repo \
--transport http \
--scope user \
--client-id your-oauth-client-id \
http://localhost:8000/mcp
Ruta de Configuración Personalizada
docker run \
-v $(pwd)/my-config.yaml:/custom/config.yaml \
-v $(pwd)/specs:/app/specs \
-e CONFIG_PATH=/custom/config.yaml \
-e AAS_COMPONENT=aas-repo \
-e AAS_BASE_URL=http://your-backend:8080 \
-i ghcr.io/sap/aas-mcp-server:latest
Autorización OAuth 2.1
El servidor soporta OAuth 2.1 + PKCE para transportes HTTP. Cuando está habilitado, el servidor valida los tokens Bearer entrantes y los reenvía al backend de AAS.
Variables de entorno
| Variable | Requerida | Descripción |
|---|---|---|
OAUTH_ISSUER_URL | Sí (para habilitar) | URL del emisor del proveedor OIDC. La autenticación está deshabilitada cuando no se establece. |
OAUTH_CLIENT_ID | Sí | ID de cliente de la aplicación registrada en el IdP ascendente. |
OAUTH_CLIENT_SECRET | Recomendada | Secreto de cliente del registro de la aplicación en el IdP. |
OAUTH_SERVER_BASE_URL | Requerida para Docker | URL pública del servidor MCP tal como la ven los clientes. Requerida cuando MCP_HOST=0.0.0.0. |
OAUTH_AUDIENCE | Recomendada | Reclamo aud esperado en los tokens. |
OAUTH_REQUIRED_SCOPES | Opcional | Alcances separados por comas solicitados al IdP durante el flujo PKCE, p. ej. aas:read,aas:write. |
OAUTH_SESSION_STORE_URL | Opcional | Almacenamiento externo para el estado de sesión OAuth. Soporta redis://, rediss://, postgresql://. El valor predeterminado es en memoria (el estado se pierde al reiniciar). |
MCP_RATE_LIMIT_PER_MINUTE | Opcional | Máximo de solicitudes por cliente por minuto. Predeterminado: 60. |
Cómo funciona
El servidor actúa como un Servidor de Autorización OAuth 2.1 a través de OIDCProxy de FastMCP. Expone un flujo PKCE estándar que se proxifica a tu IdP ascendente. Los clientes MCP (Claude CLI, OpenCode, etc.) se registran mediante Registro Dinámico de Clientes y completan un flujo PKCE basado en navegador automáticamente.
El token Bearer validado se reenvía al backend de AAS en cada solicitud saliente. Establece BACKEND_AUTH_AUDIENCE si el backend requiere una audiencia de token diferente (intercambio de tokens vía RFC 8693).
Componentes Soportados
aas-repo- Repositorio de Asset Administration Shellsubmodel-repo- Repositorio de Submodelosaas-registry- Registro de AASsubmodel-registry- Registro de Submodelos
Pruebas
Ejecuta las pruebas:
# Unit tests only
tests/run_tests.sh
# With integration tests (requires backend on port 8081)
tests/run_tests.sh --integration
Soporte, Retroalimentación, Contribuciones
Este proyecto está abierto a solicitudes de funciones/sugerencias, informes de errores, etc. a través de problemas de GitHub. Las contribuciones y la retroalimentación son bienvenidas y siempre alentadas. Para más información sobre cómo contribuir, la estructura del proyecto, así como información adicional sobre contribuciones, consulta nuestras Directrices de Contribución.
Seguridad / Divulgación
- Solo lectura por defecto - Las operaciones de escritura están deshabilitadas a menos que
--enable-writes - Basado en lista de permitidos - Solo se exponen las operaciones explícitamente permitidas
- Patrones comodín -
[get, "*"],["*", /path],["*", "*"] - Límites de paginación - Máximo 100 elementos por solicitud
Si encuentras algún error que pueda ser un problema de seguridad, sigue nuestras instrucciones en nuestra política de seguridad sobre cómo reportarlo. Por favor, no crees problemas de GitHub para dudas o problemas relacionados con la seguridad.
Cómo Funciona
Cuando el servidor se inicia, procesa la especificación OpenAPI a través de un pipeline antes de entregarla a FastMCP:
- Cargar — lee el archivo de especificación y aplica cualquier superposición (renombrar, agregar descripciones, etc.)
- Aplanar — resuelve cadenas de herencia
$refy fusiona composicionesallOfen esquemas planos. Esto es necesario porque la especificación oficial IDTA AAS usa herencia de múltiples nivelesallOf+$ref(p. ej.AssetAdministrationShell→Identifiable→Referable). Sin aplanar, FastMCP solo ve las propiedades definidas directamente en el esquema y pierde todos los campos heredados comoid,modelTypeyassetInformation. Las referencias circulares se manejan manteniendo un puntero$refen el punto del ciclo en lugar de recurrir infinitamente. - Curar — aplica la lista de permitidos para filtrar rutas, aplica el modo de solo lectura, aplica alias de ID de operación y limita los límites de paginación.
- Podar — elimina entradas
components/schemasque ya no son alcanzables desde ninguna ruta restante. Esto evita que el validador de esquemas de FastMCP procese esquemas circulares que pertenecían a rutas filtradas en el paso anterior, lo que de otro modo causaría que se colgara. - Generar — FastMCP genera herramientas MCP a partir de la especificación procesada y las conecta al cliente HTTP.
Nota sobre la fusión de
allOf: Cuando múltiples elementosallOfdefinen la misma palabra clave no relacionada con propiedades (p. ej.description,additionalProperties), se conserva el primer valor. Esto es seguro para la especificación estándar IDTA AAS pero puede producir restricciones de validación más débiles en especificaciones con palabras clave a nivel de allOf conflictivas.
Solución de Problemas
"Archivo de configuración no encontrado"
Proporciona la configuración vía:
--config /path/to/config.yaml- Variable de entorno
CONFIG_PATH - Predeterminado:
/app/config/config.yaml
"official_spec file not found"
Verifica:
- Las rutas en config.yaml son correctas
- Las especificaciones están montadas (Docker):
-v $(pwd)/specs:/app/specs
OAuth / Transporte HTTP
"Se obtuvieron nuevas credenciales, pero el servidor las rechazó al reconectar"
La validación del token está fallando dentro del contenedor. Revisa los registros del contenedor:
docker logs <container-id> 2>&1 | grep -E "OIDCProxy|401|ERROR"
Causas comunes:
1. Falta OAUTH_CLIENT_ID o OAUTH_CLIENT_SECRET
El servidor ahora usa OIDCProxy, que requiere un cliente registrado en tu IdP ascendente. Tanto OAUTH_CLIENT_ID como OAUTH_CLIENT_SECRET deben estar establecidos:
-e OAUTH_CLIENT_ID=your-client-id \
-e OAUTH_CLIENT_SECRET=your-client-secret \
2. El nombre de host no se puede resolver dentro del contenedor Docker
El nombre de host de tu IdP (p. ej. keycloak.example.localhost) puede resolverse en tu máquina
anfitriona pero no dentro del contenedor Docker. Verifica:
docker exec <container-id> python3 -c \
"import socket; print(socket.gethostbyname('your-idp-hostname'))"
Si falla, agrega el nombre de host con --add-host:
# Find the IP your IdP resolves to on the host
python3 -c "import socket; print(socket.gethostbyname('your-idp-hostname'))"
# Add it to the container
docker run --add-host your-idp-hostname:<ip> ...
Si tu configuración usa un contenedor proxy inverso nginx, usa la IP del contenedor proxy — no la IP del contenedor del IdP directamente. El proxy escucha en el puerto 80 y enruta por nombre de host; el contenedor del IdP típicamente solo escucha en un puerto alto (p. ej. 8080) y no acepta solicitudes de nombre de host simple en el puerto 80.
# Find all container names and IPs on your network
docker network inspect your-network | python3 -c "
import sys, json
data = json.load(sys.stdin)
for c in data[0].get('Containers', {}).values():
print(c['Name'], c.get('IPv4Address'))
"
# Use the proxy container's IP (e.g. nginx-proxy), not the IdP container's IP
docker run --add-host your-idp-hostname:<proxy-ip> ...
Para verificar que el nombre de host se resuelve Y llega al IdP desde dentro del contenedor:
docker exec <container-id> python3 -c "
import urllib.request
resp = urllib.request.urlopen('https://your-idp-hostname/.well-known/openid-configuration', timeout=5)
print('OIDC discovery status:', resp.status)
"
3. Discrepancia de URL resource (0.0.0.0 vs localhost)
Cuando MCP_HOST=0.0.0.0, los metadatos del recurso protegido del servidor anuncian
http://0.0.0.0:8000/mcp como su URL, que no coincide con http://localhost:8000/mcp
que el cliente MCP registró. Establece OAUTH_SERVER_BASE_URL a la URL pública:
-e OAUTH_SERVER_BASE_URL=http://localhost:8000
Verifica que los metadatos sean correctos antes de registrarte con tu cliente MCP:
curl -s http://localhost:8000/.well-known/oauth-protected-resource/mcp \
| python3 -m json.tool
# "resource" must exactly match the URL you pass to your MCP client
4. Red Docker incorrecta
El contenedor debe estar en la misma red que tu backend de AAS y el IdP:
# List container networks
docker ps --format "{{.Names}}\t{{.Networks}}"
# Use the correct network
docker run --network correct-network-name ...
"No encontrado" cuando el navegador se abre para autenticación
El cliente MCP construyó la URL de autorización usando el endpoint incorrecto. Esto
sucede cuando el /.well-known/oauth-authorization-server del servidor devuelve 404
(esperado — este servidor es un servidor de recursos puro) y el cliente retrocede
incorrectamente. Asegúrate de que --client-id se pase al registrar el servidor
(ejemplo usando Claude CLI):
claude mcp add aas-repo \
--transport http \
--scope user \
--client-id your-oauth-client-id \
http://localhost:8000/mcp
La herramienta MCP tiene éxito pero el backend de AAS devuelve 401
El servidor MCP aceptó el token pero el backend lo rechazó. Este es un fallo diferente a que el propio servidor MCP devuelva 401.
Síntoma: Una llamada a herramienta MCP devuelve algo como:
Error calling tool 'list_shells': HTTP error 401:
Revisa los registros del servidor MCP para la solicitud saliente:
docker logs <container-id> 2>&1 | grep -A3 "send_request_headers\|aas-env\|GET /shells"
Observa la línea headers. Si authorization está ausente, el reenvío de tokens
no está funcionando.
Causas comunes:
a. La versión de FastMCP cambió el comportamiento de get_http_headers()
FastMCP ≥3.3 excluye explícitamente authorization de get_http_headers().
El servidor usa BearerTokenAuth (una clase httpx.Auth personalizada) para solucionar
esto. Si ves que falta el encabezado, verifica que estás ejecutando la imagen actual:
docker exec <container-id> python3 -c "
from aas_mcp_server.http_client import BearerTokenAuth
print('BearerTokenAuth present — token forwarding is correct')
"
b. Discrepancia de issuer-uri en Spring Security
Spring Security valida el reclamo iss en el token con coincidencia exacta
de cadenas. Si el servidor MCP se configuró con
OAUTH_ISSUER_URL=http://keycloak.localhost/realms/aas pero el
backend de AAS tiene issuer-uri: http://keycloak:8080/realms/aas, Spring rechaza
el token aunque sea del mismo Keycloak.
Decodifica el token para verificar el reclamo iss:
TOKEN=<your-token>
python3 -c "
import base64, json
payload = '$TOKEN'.split('.')[1]
payload += '=' * (4 - len(payload) % 4)
print('iss:', json.loads(base64.urlsafe_b64decode(payload)).get('iss'))
"
El valor de iss debe coincidir exactamente con el issuer-uri en la configuración de
Spring Security del backend de AAS. Establece OAUTH_ISSUER_URL al valor que el
backend de AAS espera.
Probando manualmente la cadena completa de tokens
Para reproducir y aislar fallos antes de involucrar a un cliente MCP:
# 1. Get a token (replace with your provider's token endpoint)
TOKEN=$(curl -s -X POST \
"https://your-idp/realms/your-realm/protocol/openid-connect/token" \
-d "grant_type=password&client_id=your-client&username=user&password=pass&scope=openid" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# 2. Decode the token claims
python3 -c "
import base64, json
payload = '$TOKEN'.split('.')[1]
payload += '=' * (4 - len(payload) % 4)
c = json.loads(base64.urlsafe_b64decode(payload))
print('iss:', c.get('iss'))
print('aud:', c.get('aud'))
print('scope:', c.get('scope'))
"
# 3. Call the MCP server — should return 200
curl -s -o /dev/null -w "%{http_code}" \
-X POST http://localhost:8000/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1"}},"id":1}'
Código de Conducta
Nosotros, como miembros, contribuyentes y líderes, nos comprometemos a hacer que la participación en nuestra comunidad sea una experiencia libre de acoso para todos. Al participar en este proyecto, aceptas cumplir con su Código de Conducta en todo momento.
Licencia
Copyright 2026 SAP SE o una empresa afiliada a SAP y contribuyentes de aas-mcp-server. Consulta nuestra LICENCIA para información de derechos de autor y licencia. Información detallada incluyendo componentes de terceros y su información de licencia/derechos de autor está disponible a través de la herramienta REUSE.