Docs›Multi-tenancy›Row-scope vs MCP-scope
Multi-tenancy

Row-scope vs MCP-scope

tenant_id (empresa, todo o Eloquent) vs escopo do agente de IA (usuário/unidade, só MCP) — eixos ortogonais.

Row-scope (tenant) vs MCP-scope (agente de IA)

O MAD tem dois filtros de linha que soam parecidos mas resolvem problemas diferentes, em camadas diferentes da aplicação. Confundi-los é o erro mais comum ao configurar uma instalação que usa tenancy e o agente de IA ao mesmo tempo — por isso esta página existe separada de Estratégias de multi-tenancy.

Regra de ouro: o row-scope de tenancy isola por empresa. O row-scope do MCP isola por usuário/unidade, e só dentro do agente de IA. São eixos ortogonais — uma instalação pode ter os dois ligados ao mesmo tempo, e um nunca substitui o outro.

Comparação direta

Row-scope de tenant (Pool) Row-scope do MCP
Isola por empresa (tenant_id) usuário ou unidade que está conversando com o agente
Onde age qualquer leitura/escrita Eloquent nos models do plano de dados só nas tools do agente de IA (camada MCP)
Coluna usada tenant_id (fixa, uma por tabela) owner_column / unit_column, declaradas por tabela — nomes variam (created_by, user_id, created_by_unit_id, ...)
Liga em MAD_TENANT_ROW_SCOPE_ENABLED (.env) MAD_MCP_ROW_SCOPE_ENABLED (.env)
Mecanismo trait Mad\Database\Concerns\BelongsToTenant (global scope Eloquent) McpScopedGateway, resolvendo o plano de acesso via McpGrantResolver a partir do manifesto MCP
Nível ligado/desligado por instalação granular: own (só o que é meu) | unit (da minha unidade) | all (sem filtro), resolvido por permissão do usuário
Escopo de empresa é o próprio eixo — N empresas na mesma instalação nenhum: assume single-tenant por instalação; não filtra por tenant_id

Por que não dá para usar a mesma coluna

O row-scope de tenant pressupõe uma coluna fixa (tenant_id) presente nas tabelas do plano de dados (business, comm, ged, ai) — é sobre isolar clientes da mesma instalação entre si.

O row-scope do MCP resolve um problema diferente: dentro de uma mesma empresa, o usuário A não deve ver, através do agente de IA, uma linha que só o usuário B (ou só a unidade de B) deveria acessar — mesmo que ambos pertençam ao mesmo tenant. Por tabela exposta ao agente, o runtime espera declarar qual coluna identifica o "dono" da linha:

// mcp.config.json — bloco "scope" (resumo do contrato)
"scope": {
  "ged_document": {
    "owner_column": "created_by_user_id",  // FK inteira para o usuário dono
    "unit_column":  "created_by_unit_id",
    "allow_coarsen": false                 // nível 'own' pode cair p/ unidade?
  },
  "ged_tag": {
    "exempt": true                         // tabela de referência — sem dono, sem filtro
  }
}

Se uma tabela é exposta ao agente e nenhuma política está declarada — sem bloco scope, ou bloco sem owner_column/unit_column e sem exempt — o runtime nega o acesso ("entidade exposta sem politica de row-scope"): fail-closed, não fail-open. exempt só isenta com true explícito.

O nível efetivo é resolvido por permissão do usuário (Mad\Mcp\McpGrantResolver), não por configuração de tenant:

Nível Como o usuário ganha Efeito
all acesso ao programa de mcp.row_scope_bypass_program (MAD_MCP_ROW_SCOPE_BYPASS_PROGRAM) sem filtro (god-read auditado)
unit acesso ao programa de mcp.row_scope_unit_program (MAD_MCP_ROW_SCOPE_UNIT_PROGRAM) filtra por unit_column; só com owner_column, cai para "dono = eu"
own default (nenhum dos dois) filtra por owner_column; tabela que só tem unit_column é negada, salvo allow_coarsen: true

Ambos os programas vazios (o default) = ninguém sobe de nível: todo mundo em own.

As duas flags, lado a lado

# Tenancy — isola EMPRESAS entre si, afeta todo o Eloquent
MAD_TENANT_ROW_SCOPE_ENABLED=true

# MCP — isola USUÁRIO/UNIDADE dentro do agente de IA, afeta só as tools MCP
MAD_MCP_ROW_SCOPE_ENABLED=true

Uma instalação pode ligar as duas: o tenant_id já reduz o universo de linhas à empresa do usuário (independente de tenancy ser Pool ou Bridge — no Bridge o isolamento já é físico, por banco), e o row-scope do MCP, por cima disso, reduz ainda mais ao que aquele usuário específico pode tocar via chat/agente.

O row-scope do MCP assume single-tenant por instalação. Ele não conhece tenant_id — o isolamento entre empresas tem que vir da camada de tenancy (Pool via BelongsToTenant, ou Bridge repointando o data-plane). Se o agente for exposto numa instalação multi-empresa, ligue a tenancy: o own/unit do MCP sozinho não impede uma tabela sem tenant_id de misturar empresas.

Contexto: a tela de configuração visual

Builders visuais do ecossistema MAD (geradores de aplicação) costumam expor essas duas camadas — engine de banco, estratégia de tenancy e escopo do agente de IA — como blocos separados de uma mesma tela de configuração de projeto, com um comando ou diff de .env/config/mad.php gerado a partir das escolhas. Isso é tooling de geração, não uma API que sua aplicação chama em runtime: o que efetivamente importa para o código que você escreve são as flags e os arquivos descritos nesta página — .env, config/mad.php e mcp.config.json. Se você está configurando isso à mão, o que muda na prática é exatamente o que está documentado aqui e em Estratégias de multi-tenancy.

Ver também

  • Estratégias de multi-tenancy — Single, Pool, Bridge, Hybrid, a tela de Empresas (CRUD + Provisionar), a seleção de empresa no login e o middleware mad.api.
  • CLI — mad:mcp:lint-scope valida o bloco scope do mcp.config.json contra o schema real do banco.