xpeditis2.0/apps/backend/src/application/mcp/capabilities/admin.capabilities.ts
David 177b70ea96 feat(api): registre de capacites et serveur MCP
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
2026-09-07 21:40:54 +02:00

127 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { UserRepository } from '@domain/ports/out/user.repository';
import { OrganizationRepository } from '@domain/ports/out/organization.repository';
import { CsvRateSearchService } from '@domain/services/csv-rate-search.service';
import { Capability } from '../capability';
/** Role d'administration de la plateforme. Les inscriptions creent des MANAGER. */
const ADMIN_ONLY = ['ADMIN'] as const;
/**
* Capacites d'administration de la plateforme.
*
* Elles franchissent la frontiere de l'organisation — c'est precisement ce qui
* les distingue du reste du catalogue — et sont donc reservees au role ADMIN,
* verifie dans le processus et non dans un prompt.
*
* Elles sont **en lecture seule**. Modifier un utilisateur, valider un SIRET ou
* remplacer une grille tarifaire touche des comptes clients et de l'argent :
* ces actions restent a la main d'une personne, dans l'espace d'administration,
* tant qu'un mecanisme de confirmation explicite n'existe pas cote agent.
*/
export function adminCapabilities(
users: UserRepository,
organizations: OrganizationRepository,
rateSearch: CsvRateSearchService
): Capability[] {
return [
{
policy: { name: 'admin_list_users', scope: 'read', roles: ADMIN_ONLY },
description:
"Liste les comptes de la plateforme, toutes organisations confondues. Réservé à l'administration.",
inputSchema: {
type: 'object',
properties: {
role: {
type: 'string',
description: 'Ne garder que ce rôle.',
enum: ['ADMIN', 'MANAGER', 'USER', 'VIEWER', 'CARRIER'],
},
search: {
type: 'string',
description: 'Filtre sur l’adresse e-mail ou le nom.',
maxLength: 120,
},
limit: {
type: 'integer',
description: 'Nombre maximum de comptes.',
minimum: 1,
maximum: 100,
default: 25,
},
},
additionalProperties: false,
},
handler: async input => {
const all = input.role
? await users.findByRole(input.role as string)
: await users.findAll();
const term = (input.search as string | undefined)?.toLowerCase();
const matching = term
? all.filter(user =>
`${user.email} ${user.firstName} ${user.lastName}`.toLowerCase().includes(term)
)
: all;
return {
total: matching.length,
users: matching.slice(0, (input.limit as number) ?? 25).map(user => ({
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
role: user.role,
organizationId: user.organizationId,
isActive: user.isActive,
})),
};
},
},
{
policy: { name: 'admin_list_organizations', scope: 'read', roles: ADMIN_ONLY },
description:
"Liste les organisations de la plateforme, avec leur nombre de comptes. Réservé à l'administration.",
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'integer',
description: "Nombre maximum d'organisations.",
minimum: 1,
maximum: 100,
default: 25,
},
},
additionalProperties: false,
},
handler: async input => {
const all = await organizations.findAll();
const page = all.slice(0, (input.limit as number) ?? 25);
return {
total: all.length,
organizations: await Promise.all(
page.map(async organization => ({
id: organization.id,
name: organization.name,
userCount: await users.countByOrganization(organization.id),
}))
),
};
},
},
{
policy: { name: 'admin_rate_grid_overview', scope: 'read', roles: ADMIN_ONLY },
description:
"État des grilles tarifaires chargées : transporteurs et types de conteneurs disponibles. Réservé à l'administration.",
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
handler: async () => ({
carriers: await rateSearch.getAvailableCompanies(),
containerTypes: await rateSearch.getAvailableContainerTypes(),
}),
},
];
}