AAS MCP Server

Um adaptador MCP AAS que expõe APIs configuradas de Asset Administration Shell como ferramentas do Model Context Protocol, permitindo que agentes de LLM interajam com qualquer backend compatível com AAS.

Documentação

REUSE status

AAS MCP Server

Sobre este projeto

Ponte OpenAPI-to-MCP para APIs de Asset Administration Shell (AAS)

Um adaptador AAS MCP que expõe APIs configuradas de Asset Administration Shell como ferramentas do Model Context Protocol, permitindo que agentes de LLM interajam com qualquer backend compatível com AAS.

License Python 3.12+

Requisitos e Configuração

Pré-requisitos

  1. Especificações OpenAPI AAS - Baixar do GitHub
  2. Servidor Backend AAS - Servidor AAS SAP BNAC, Eclipse BaSyx, Serviço FA³ST, etc.
  3. Python 3.12+ OU Docker

Configuração

  1. Obter Especificações AAS:

    mkdir specs && cd specs
    # Download from https://github.com/admin-shell-io/aas-specs/tree/main/schemas/openapi
    
  2. Criar config.yaml (copiar de 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. Executar:

    # 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
    

    A imagem é publicada como multi-arquitetura (linux/amd64, linux/arm64) em ghcr.io/sap/aas-mcp-server. Tags disponíveis: latest, <major>.<minor> (ex.: 0.1) e <version> (ex.: 0.1.0). Não é necessário login — o pacote é público.

Configuração

Básica (Somente Especificação Oficial)

components:
  aas-repo:
    official_spec: specs/aas-repo-spec.yaml

Filtrada (Específica da Implementação)

Filtre para apenas os endpoints que seu backend suporta:

components:
  aas-repo:
    official_spec: specs/aas-repo-official.yaml
    implementation_spec: specs/aas-supported-endpoints.yaml

Resultado: Apenas endpoints em ambas as especificações são expostos (interseção).

Com Curadoria (Suporte a Curingas)

Controle quais operações são expostas usando curingas:

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

Consulte config.yaml.template para opções completas.

Configuração do Cliente MCP

O mesmo binário aas-mcp-server funciona com todos os clientes compatíveis com MCP — a lógica do servidor é idêntica, apenas o formato de configuração difere por cliente. Exemplos completos para todos os clientes estão disponíveis em client_config_examples.txt.

Claude Desktop

Edite ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\Claude\claude_desktop_config.json (Windows). Consulte claude_desktop_config.example.json para o exemplo completo de quatro 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

Opções de escopo: --scope local (padrão, projeto atual), --scope user (todos os projetos), --scope project (compartilhado com a equipe via .mcp.json).

OpenCode

Adicione a opencode.json na raiz do seu projeto:

{
  "$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" }
    }
  }
}

Consulte client_config_examples.txt para todos os quatro componentes, configuração de autenticação e configuração de modo de escrita para cada cliente.

Uso com Docker

A imagem pré-construída é publicada no GitHub Container Registry em ghcr.io/sap/aas-mcp-server (multi-arquitetura linux/amd64 + linux/arm64, com SBOM e proveniência de build atestada). Os exemplos abaixo usam :latest; fixe uma versão específica (:0.1.0) ou linha menor (:0.1) para implantações reproduzíveis.

Básico (stdio — uso local, sem autenticação)

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 com OAuth 2.1

Para implantações remotas onde clientes MCP se conectam pela rede:

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

Registre com um cliente MCP (exemplo usando Claude CLI):

claude mcp add aas-repo \
  --transport http \
  --scope user \
  --client-id your-oauth-client-id \
  http://localhost:8000/mcp

Caminho de Configuração Personalizado

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

Autorização OAuth 2.1

O servidor suporta OAuth 2.1 + PKCE para transportes HTTP. Quando habilitado, o servidor valida tokens Bearer de entrada e os encaminha para o backend AAS.

Variáveis de ambiente

VariávelObrigatóriaDescrição
OAUTH_ISSUER_URLSim (para habilitar)URL do emissor do provedor OIDC. A autenticação é desabilitada quando não definida.
OAUTH_CLIENT_IDSimID do cliente do aplicativo registrado no IdP upstream.
OAUTH_CLIENT_SECRETRecomendadaSegredo do cliente do registro do aplicativo no IdP.
OAUTH_SERVER_BASE_URLObrigatória para DockerURL pública do servidor MCP conforme vista pelos clientes. Obrigatória quando MCP_HOST=0.0.0.0.
OAUTH_AUDIENCERecomendadaClaim aud esperada nos tokens.
OAUTH_REQUIRED_SCOPESOpcionalEscopos separados por vírgula solicitados ao IdP durante o fluxo PKCE, ex.: aas:read,aas:write.
OAUTH_SESSION_STORE_URLOpcionalArmazenamento externo para estado de sessão OAuth. Suporta redis://, rediss://, postgresql://. Padrão: em memória (estado perdido ao reiniciar).
MCP_RATE_LIMIT_PER_MINUTEOpcionalMáximo de solicitações por cliente por minuto. Padrão: 60.

Como funciona

O servidor atua como um Servidor de Autorização OAuth 2.1 via OIDCProxy do FastMCP. Ele expõe um fluxo PKCE padrão que faz proxy para seu IdP upstream. Clientes MCP (Claude CLI, OpenCode, etc.) registram-se via Dynamic Client Registration e completam um fluxo PKCE baseado em navegador automaticamente.

O token Bearer validado é encaminhado ao backend AAS em cada solicitação de saída. Defina BACKEND_AUTH_AUDIENCE se o backend exigir um público de token diferente (troca de token via RFC 8693).

Componentes Suportados

  • aas-repo - Repositório de Asset Administration Shell
  • submodel-repo - Repositório de Submodelos
  • aas-registry - Registro AAS
  • submodel-registry - Registro de Submodelos

Testes

Execute os testes:

# Unit tests only
tests/run_tests.sh

# With integration tests (requires backend on port 8081)
tests/run_tests.sh --integration

Suporte, Feedback, Contribuições

Este projeto está aberto a solicitações de recursos/sugestões, relatórios de bugs, etc. via issues do GitHub. Contribuições e feedback são incentivados e sempre bem-vindos. Para mais informações sobre como contribuir, a estrutura do projeto, bem como informações adicionais sobre contribuição, consulte nossas Diretrizes de Contribuição.

Segurança / Divulgação

  • Somente leitura por padrão - Operações de escrita desabilitadas a menos que --enable-writes
  • Baseado em lista de permissões - Apenas operações explicitamente permitidas são expostas
  • Padrões curinga - [get, "*"], ["*", /path], ["*", "*"]
  • Limites de paginação - Máximo de 100 itens por solicitação

Se você encontrar qualquer bug que possa ser um problema de segurança, siga nossas instruções em nossa política de segurança sobre como reportá-lo. Por favor, não crie issues no GitHub para dúvidas ou problemas relacionados à segurança.

Como Funciona

Quando o servidor inicia, ele processa a especificação OpenAPI através de um pipeline antes de entregá-la ao FastMCP:

  1. Carregar — lê o arquivo de especificação e aplica qualquer overlay (renomear, adicionar descrições, etc.)
  2. Achatar — resolve cadeias de herança $ref e mescla composições allOf em esquemas planos. Isso é necessário porque a especificação oficial IDTA AAS usa herança allOf + $ref de múltiplos níveis (ex.: AssetAdministrationShell → Identifiable → Referable). Sem o achatamento, o FastMCP vê apenas as propriedades definidas diretamente no esquema e perde todos os campos herdados como id, modelType e assetInformation. Referências circulares são tratadas mantendo um ponteiro $ref no ponto do ciclo em vez de recorrer infinitamente.
  3. Curar — aplica a lista de permissões para filtrar caminhos, aplica o modo somente leitura, aplica aliases de IDs de operação e limita os limites de paginação.
  4. Podar — remove entradas components/schemas que não são mais alcançáveis de nenhum caminho restante. Isso evita que o validador de esquemas do FastMCP processe esquemas circulares que pertenciam a caminhos filtrados na etapa anterior, o que de outra forma causaria travamento.
  5. Gerar — o FastMCP gera ferramentas MCP a partir da especificação processada e as conecta ao cliente HTTP.

Nota sobre mesclagem allOf: Quando múltiplos elementos allOf definem a mesma palavra-chave não-propriedade (ex.: description, additionalProperties), o primeiro valor é mantido. Isso é seguro para a especificação padrão IDTA AAS, mas pode produzir restrições de validação mais fracas em especificações com conflitos de palavras-chave no nível allOf.

Solução de Problemas

"Arquivo de configuração não encontrado"

Forneça a configuração via:

  • --config /path/to/config.yaml
  • Variável de ambiente CONFIG_PATH
  • Padrão: /app/config/config.yaml

"Arquivo official_spec não encontrado"

Verifique:

  • Se os caminhos em config.yaml estão corretos
  • Se as especificações estão montadas (Docker): -v $(pwd)/specs:/app/specs

OAuth / Transporte HTTP

"Novas credenciais obtidas, mas o servidor as rejeitou na reconexão"

A validação do token está falhando dentro do contêiner. Verifique os logs do contêiner:

docker logs <container-id> 2>&1 | grep -E "OIDCProxy|401|ERROR"

Causas comuns:

1. OAUTH_CLIENT_ID ou OAUTH_CLIENT_SECRET ausentes

O servidor agora usa OIDCProxy, que requer um cliente registrado no seu IdP upstream. Tanto OAUTH_CLIENT_ID quanto OAUTH_CLIENT_SECRET devem ser definidos:

-e OAUTH_CLIENT_ID=your-client-id \
-e OAUTH_CLIENT_SECRET=your-client-secret \

2. Nome de host não resolvível dentro do contêiner Docker

O nome de host do seu IdP (ex.: keycloak.example.localhost) pode resolver na sua máquina host, mas não dentro do contêiner Docker. Verifique:

docker exec <container-id> python3 -c \
  "import socket; print(socket.gethostbyname('your-idp-hostname'))"

Se falhar, adicione o nome de host com --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> ...

Se sua configuração usa um contêiner de proxy reverso nginx, use o IP do contêiner proxy — não o IP do contêiner do IdP diretamente. O proxy escuta na porta 80 e roteia por nome de host; o contêiner do IdP normalmente escuta apenas em uma porta alta (ex.: 8080) e não aceita solicitações de nome de host simples na porta 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 se o nome de host resolve E alcança o IdP de dentro do contêiner:

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. Incompatibilidade de URL resource (0.0.0.0 vs localhost)

Quando MCP_HOST=0.0.0.0, os metadados de recurso protegido do servidor anunciam http://0.0.0.0:8000/mcp como sua URL, que não corresponde a http://localhost:8000/mcp que o cliente MCP registrou. Defina OAUTH_SERVER_BASE_URL para a URL voltada ao público:

-e OAUTH_SERVER_BASE_URL=http://localhost:8000

Verifique se os metadados estão corretos antes de registrar com seu 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. Rede Docker errada

O contêiner deve estar na mesma rede que seu backend AAS e IdP:

# List container networks
docker ps --format "{{.Names}}\t{{.Networks}}"

# Use the correct network
docker run --network correct-network-name ...

"Não encontrado" quando o navegador abre para autenticação

O cliente MCP construiu a URL de autorização usando o endpoint errado. Isso acontece quando o /.well-known/oauth-authorization-server do servidor retorna 404 (esperado — este servidor é um servidor de recursos puro) e o cliente faz fallback incorretamente. Garanta que --client-id seja passado ao registrar o servidor (exemplo usando Claude CLI):

claude mcp add aas-repo \
  --transport http \
  --scope user \
  --client-id your-oauth-client-id \
  http://localhost:8000/mcp

Ferramenta MCP é bem-sucedida, mas o backend AAS retorna 401

O servidor MCP aceitou o token, mas o backend o rejeitou. Esta é uma falha diferente do servidor MCP em si retornando 401.

Sintoma: Uma chamada de ferramenta MCP retorna algo como:

Error calling tool 'list_shells': HTTP error 401:

Verifique os logs do servidor MCP para a solicitação de saída:

docker logs <container-id> 2>&1 | grep -A3 "send_request_headers\|aas-env\|GET /shells"

Observe a linha headers. Se authorization estiver ausente, o encaminhamento de token não está funcionando.

Causas comuns:

a. A versão do FastMCP mudou o comportamento de get_http_headers()

FastMCP ≥3.3 exclui explicitamente authorization de get_http_headers(). O servidor usa BearerTokenAuth (uma classe httpx.Auth personalizada) para contornar isso. Se você vir o cabeçalho ausente, verifique se está executando a imagem atual:

docker exec <container-id> python3 -c "
from aas_mcp_server.http_client import BearerTokenAuth
print('BearerTokenAuth present — token forwarding is correct')
"

b. Incompatibilidade de issuer-uri do Spring Security

O Spring Security valida a claim iss no token com correspondência exata de string. Se o servidor MCP foi configurado com OAUTH_ISSUER_URL=http://keycloak.localhost/realms/aas mas o backend AAS tem issuer-uri: http://keycloak:8080/realms/aas, o Spring rejeita o token mesmo que seja do mesmo Keycloak.

Decodifique o token para verificar a claim 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'))
"

O valor de iss deve corresponder exatamente ao issuer-uri na configuração do Spring Security do backend AAS. Defina OAUTH_ISSUER_URL para o valor que o backend AAS espera.

Testando manualmente a cadeia completa de tokens

Para reproduzir e isolar falhas antes de envolver um 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 Conduta

Nós, como membros, contribuidores e líderes, nos comprometemos a tornar a participação em nossa comunidade uma experiência livre de assédio para todos. Ao participar deste projeto, você concorda em cumprir seu Código de Conduta em todos os momentos.

Licenciamento

Copyright 2026 SAP SE ou uma empresa afiliada à SAP e contribuidores do aas-mcp-server. Consulte nossa LICENÇA para informações de direitos autorais e licença. Informações detalhadas, incluindo componentes de terceiros e suas informações de licenciamento/direitos autorais, estão disponíveis via ferramenta REUSE.