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 viaBelongsToTenant, ou Bridge repointando o data-plane). Se o agente for exposto numa instalação multi-empresa, ligue a tenancy: oown/unitdo MCP sozinho não impede uma tabela semtenant_idde 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-scopevalida o blocoscopedomcp.config.jsoncontra o schema real do banco.