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

REUSE status

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.

License Python 3.12+

Requisitos y Configuración

Prerrequisitos

  1. Especificaciones OpenAPI de AAS - Descargar desde GitHub
  2. Servidor Backend de AAS - Servidor SAP BNAC AAS, Eclipse BaSyx, Servicio FA³ST, etc.
  3. Python 3.12+ O Docker

Configuración

  1. Obtener las Especificaciones de AAS:

    mkdir specs && cd specs
    # Download from https://github.com/admin-shell-io/aas-specs/tree/main/schemas/openapi
    
  2. 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]
    
  3. 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.yaml
    

    La imagen se publica como multi-arquitectura (linux/amd64, linux/arm64) en ghcr.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

VariableRequeridaDescripción
OAUTH_ISSUER_URLSí (para habilitar)URL del emisor del proveedor OIDC. La autenticación está deshabilitada cuando no se establece.
OAUTH_CLIENT_IDSíID de cliente de la aplicación registrada en el IdP ascendente.
OAUTH_CLIENT_SECRETRecomendadaSecreto de cliente del registro de la aplicación en el IdP.
OAUTH_SERVER_BASE_URLRequerida para DockerURL pública del servidor MCP tal como la ven los clientes. Requerida cuando MCP_HOST=0.0.0.0.
OAUTH_AUDIENCERecomendadaReclamo aud esperado en los tokens.
OAUTH_REQUIRED_SCOPESOpcionalAlcances separados por comas solicitados al IdP durante el flujo PKCE, p. ej. aas:read,aas:write.
OAUTH_SESSION_STORE_URLOpcionalAlmacenamiento 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_MINUTEOpcionalMá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 Shell
  • submodel-repo - Repositorio de Submodelos
  • aas-registry - Registro de AAS
  • submodel-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:

  1. Cargar — lee el archivo de especificación y aplica cualquier superposición (renombrar, agregar descripciones, etc.)
  2. Aplanar — resuelve cadenas de herencia $ref y fusiona composiciones allOf en esquemas planos. Esto es necesario porque la especificación oficial IDTA AAS usa herencia de múltiples niveles allOf + $ref (p. ej. AssetAdministrationShell → Identifiable → Referable). Sin aplanar, FastMCP solo ve las propiedades definidas directamente en el esquema y pierde todos los campos heredados como id, modelType y assetInformation. Las referencias circulares se manejan manteniendo un puntero $ref en el punto del ciclo en lugar de recurrir infinitamente.
  3. 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.
  4. Podar — elimina entradas components/schemas que 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.
  5. 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 elementos allOf definen 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.