MCP Fusion/Security and governance/Conectores multi-tenant

Conectores multi-tenant

Pregunta a la IA sobre Vinkius

Un conector, muchos tenants, ningún módulo especial: resolución del tenant por solicitud, visibilidad de capacidades filtrada por etiquetas y Presenters basados en roles que muestran a cada tenant lo que puede ver.

Atender a varios clientes desde un solo conector no es una función que se añade después; es una propiedad de cómo se construyen el contexto, la visibilidad y la percepción. MCP Fusion no tiene un módulo de tenancy porque las piezas que ya usas se combinan en uno. Esta página es la receta.

1. Resuelve el tenant por solicitud

Cada solicitud empieza con un ctx nuevo, así que el tenant se decide una vez, antes de cualquier handler:

typescript
interface AppContext {
  db: PrismaClient;
  tenantId: string;
  role: 'viewer' | 'admin';
}

registry.attachToServer(server, {
  contextFactory: async (extra) => {
    const claims = await verify(extra.session?.authToken);
    return {
      db: pool.forTenant(claims.tenant_id),
      tenantId: claims.tenant_id,
      role: claims.role,
    };
  },
});

Dos reglas lo hacen seguro. La factory devuelve un objeto nuevo por solicitud: ninguna solicitud puede observar el contexto de otra. El middleware escribe en ese objeto con protecciones contra la contaminación del prototipo, de modo que un nombre de argumento malicioso no pueda alcanzar __proto__. Los handlers usan ctx.db o ctx.tenantId como única puerta a los datos, y el id del tenant nunca procede de los argumentos de la herramienta.

2. Limita las capacidades por etiqueta

Los tenants rara vez reciben la misma superficie. Las etiquetas y un filtro deciden qué existe para cada uno:

typescript
const filterFor = (role: string) =>
  role === 'admin'
    ? { anyTag: ['public', 'admin'] }
    : { tags: ['public'], exclude: ['internal'] };

attachToServer(server, { filter: filterFor(ctx.role) });

Con esta forma, un despliegue gratuito expone herramientas públicas, uno empresarial expone herramientas administrativas y el código de los handlers es idéntico. El filtro se evalúa donde se construye la lista de herramientas, bajo el contexto propio de la solicitud. Consulta Tool exposition.

3. Da forma a la percepción por rol

La visibilidad decide qué herramientas ve un rol; la percepción decide qué muestran esas herramientas. El patrón verificado consiste en dos herramientas sobre un handler, seleccionadas por el filtro del paso 2:

typescript
const listPeople = async (input, ctx) => ctx.db.people.findMany();

f.query('people.list_public')
  .describe('List people in the tenant, without contact details')
  .tags('public')
  .returns(ViewerPresenter)          // schema without email or phone
  .handle(listPeople);

f.query('people.list_admin')
  .describe('List people in the tenant with full records')
  .tags('admin')
  .returns(AdminPresenter)
  .handle(listPeople);               // same handler function

Un agente con rol viewer solo recibe people.list_public y nunca ve campos de contacto; un agente admin recibe ambas, con el Presenter completo. Las reglas de campos y la redacción viven en un solo lugar por audiencia, y el digest del contrato registra ambas superficies. Las reglas contextuales, escritas como (data, ctx) => string[], añaden orientación dependiente del rol. Consulta Models and Presenters.

4. Aísla las credenciales por tenant

Las claves de proveedores tampoco deben compartirse entre tenants. Decláralas con defineCredentials y léelas por solicitud: en Vinkius Cloud el runtime inyecta los secretos de cada comprador en cada solicitud y, en local, el mismo accessor lee tu entorno. Consulta Credentials.

5. Atribuye coste y tráfico

Cada llamada puede atribuirse: pasa ctx.tenantId como clave de rate limit para que un tenant no agote el presupuesto de otro y emite el id del tenant en eventos de auditoría y telemetría. En la consola de Vinkius, el mismo conector muestra coste y fiabilidad por servidor, documentados en AI spend y Tool reliability.

El patrón en una tabla

ConcernMechanismWhere it lives
IdentityJWT, API key or OAuth tokencontextFactory + auth middleware
Data isolationper-tenant client from the contextcontextFactory
Capability visibilitytags + filtertool list construction
Field isolationrole-based PresentersPresenter per audience
Secret isolationBYOC credentialsruntime injection
Fair userate-limit key from ctxrate limiter middleware
Proofcontract digest + audit eventslockfile and audit sink

No hay un interruptor de runtime que olvidar ni una caché entre tenants que configurar mal, porque nada se comparte salvo que lo compartas explícitamente. Esa es la diferencia entre un conector multi-tenant y uno single-tenant con una columna de tenant.

Próximos pasos