Skip to content

ALT-37: Exponer backend como MCP server con 6 tools y RBAC#6

Merged
lapc506 merged 2 commits intomainfrom
ALT-37-backend-mcp-server
Mar 28, 2026
Merged

ALT-37: Exponer backend como MCP server con 6 tools y RBAC#6
lapc506 merged 2 commits intomainfrom
ALT-37-backend-mcp-server

Conversation

@lapc506
Copy link
Copy Markdown
Collaborator

@lapc506 lapc506 commented Mar 28, 2026

Summary

  • Crea McpModule con @modelcontextprotocol/sdk (StreamableHTTPServerTransport)
  • Implementa 6 tools: search-animals, get-subsidy-status, list-rescue-requests, get-foster-home-capacity, create-abuse-report, get-municipal-kpis
  • RBAC por tool (GOVERNMENT_ADMIN, RESCUER, VET, etc.), SUPER_USER bypass
  • JWT auth obligatorio en POST /mcp, 15 tests nuevos

Linear Issue

ALT-37

OpenSpec Change

openspec/changes/backend-mcp-server/ (6 fases completas)

Files Changed (8)

  • New: mcp.module.ts, mcp.service.ts, mcp.controller.ts, mcp.service.spec.ts, mcp.controller.spec.ts
  • Modified: app.module.ts, package.json, mcp.json

Test plan

  • Verificar npm run build compila sin errores
  • Verificar 47 tests pasan (15 nuevos)
  • Verificar POST /mcp sin token retorna 401
  • Verificar tools/list retorna 6 herramientas
  • Verificar RBAC restringe get-municipal-kpis a GOVERNMENT_ADMIN
  • Probar conexion desde Claude Code con mcp.json

Created by Claude Code on behalf of @lapc506

🤖 Generated with Claude Code

Summary by CodeRabbit

Notas de Lanzamiento

  • Nuevas Características

    • Se agregó integración de servidor MCP con autenticación basada en tokens para solicitudes seguras.
    • Se añadieron herramientas de MCP para búsqueda de animales, consultas de subsidios, listados de rescates, capacidad de hogares de acogida, reportes de abuso y métricas municipales.
    • Implementación de control de acceso basado en roles para operaciones autorizadas.
  • Chores

    • Se actualizaron las dependencias del proyecto para incluir paquetes MCP y validación adicionales.

@linear
Copy link
Copy Markdown

linear bot commented Mar 28, 2026

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@lapc506
Copy link
Copy Markdown
Collaborator Author

lapc506 commented Mar 28, 2026

@greptile review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

Warning

Rate limit exceeded

@lapc506 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 3 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 3 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 771dc664-2895-4fed-ad12-cab0c01f44e6

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa41f3 and 8e0dcac.

📒 Files selected for processing (1)
  • apps/backend/src/mcp/mcp.service.ts
📝 Walkthrough

Walkthrough

Se introduce la integración del Protocolo de Contexto Modelo (MCP) en la aplicación backend de NestJS, agregando un nuevo módulo MCP con controlador y servicio que implementa seis herramientas (búsqueda de animales, estado de subsidios, solicitudes de rescate, capacidad de hogares de acogida, reportes de abuso y KPIs municipales) con autenticación y autorización basada en roles.

Changes

Cohort / File(s) Summary
Dependencias
apps/backend/package.json
Se agregan @modelcontextprotocol/sdk (^1.28.0) y zod (^4.3.6) como dependencias de runtime; se aplica formato de coma final.
Configuración de aplicación
apps/backend/src/app.module.ts
Se importa y agrega McpModule a la lista de imports del módulo; se corrige un cierre de llave redundante en la fábrica de CacheModule.registerAsync.
Implementación MCP
apps/backend/src/mcp/mcp.service.ts, apps/backend/src/mcp/mcp.controller.ts, apps/backend/src/mcp/mcp.module.ts
Se crea el módulo MCP con servicio que registra seis herramientas (búsqueda de animales, estado de subsidios, solicitudes de rescate, capacidad de hogares, creación de reportes de abuso, KPIs municipales), controlador que maneja autenticación Bearer y transporte HTTP stateless, y módulo que conecta dependencias.
Pruebas MCP
apps/backend/src/mcp/mcp.controller.spec.ts, apps/backend/src/mcp/mcp.service.spec.ts
Se agregan suites de pruebas Jest/NestJS que validan autenticación, manejo de headers, respuestas HTTP, inicialización del servidor MCP, y comportamiento de las seis herramientas con diferentes roles de usuario.
Configuración remota
mcp.json
Se agrega entrada de servidor MCP altrupets-backend que ejecuta mcp-remote apuntando a http://localhost:3001/mcp con variable de entorno AUTHORIZATION basada en JWT.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant McpController
    participant AuthService
    participant McpService
    participant MCP Server
    participant UnderlyingServices as UnderlyingServices<br/>(Animals, Subsidies, etc.)

    Client->>McpController: POST /mcp con Authorization header
    McpController->>AuthService: validateToken(token)
    alt Token válido
        AuthService-->>McpController: ✓ válido
        McpController->>McpController: setCurrentRequestUser(user)
        McpController->>McpService: getServer()
        McpService-->>McpController: MCP Server instance
        McpController->>MCP Server: handleRequest(req, res, body)
        MCP Server->>MCP Server: Procesa herramienta MCP
        MCP Server->>UnderlyingServices: Ejecuta servicio<br/>(findAll, findOne, etc.)
        UnderlyingServices-->>MCP Server: Resultado
        MCP Server-->>Client: Respuesta JSON-RPC
    else Token inválido/ausente
        AuthService-->>McpController: ✗ rechazado
        McpController-->>Client: 401 + JSON-RPC error (code: -32001)
    end
    McpController->>McpController: setCurrentRequestUser(null)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed El título describe claramente la adición del backend como servidor MCP con 6 herramientas e implementación de RBAC, siendo consistente con el contenido principal del PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ALT-37-backend-mcp-server

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
apps/backend/src/mcp/mcp.service.spec.ts (1)

70-170: Estas specs aún no ejercitan las tools registradas.

El bloque llama animalsService.findAll(), findByStatus(), subsidiesService.findOne(), etc. directamente, así que no cubre registerTool(), el mapping de argumentos ni el RBAC real de McpService. Incluso la prueba de inicialización sólo comprueba que exista connect(), no que haya 6 tools registradas. Conviene disparar tools/call contra el McpServer/transport o, como mínimo, ejecutar los callbacks registrados.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/mcp/mcp.service.spec.ts` around lines 70 - 170, Las pruebas
actualmente llaman los servicios directamente en vez de invocar las herramientas
registradas por McpService; cambia los tests para inicializar con
service.onModuleInit(), obtener el McpServer con service.getServer() y disparar
los handlers registrados (en vez de llamar
animalsService.findAll/findByStatus/subsidiesService.findOne/ etc. directamente)
ya sea invocando los callbacks que McpServer registra vía registerTool() o
enviando una petición de tipo "tools/call" al transport del servidor; usa
setCurrentRequestUser(...) para cada escenario antes de invocar el handler y
luego verifica que los mocks (animalsService, subsidiesService, captureStorage,
casasCunasService, abuseReportsService, jurisdictionsService) hayan sido
llamados con los argumentos esperados.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/src/mcp/mcp.service.ts`:
- Around line 302-340: The KPI aggregation in the method that calls
jurisdictionsService.findOne currently uses casasCunasService.findAll() and
animalsService.findAll() (variables casasCunas, animals, totalCapacity,
totalOccupied, kpis) so results are global and loaded into memory; change those
calls to scoped queries that filter by args.jurisdictionId (e.g., replace
findAll() with a service method like findByJurisdiction(jurisdictionId) or add a
filter argument) and, where possible, replace in-memory reduces with database
aggregate/count queries (sum capacity, sum currentCount, count by status) to
compute totalCapacity, totalOccupied and animals counts without loading all
entities into memory. Ensure the kpis.jurisdiction still uses
jurisdictionsService.findOne and that the fosterHomes and animals totals use the
filtered/aggregated results.
- Around line 212-236: El handler valida un argumento jurisdictionId en
inputSchema pero nunca lo usa: actualmente siempre llama
this.casasCunasService.findAll() y serializa todas las casas cuna
(capacityData), por lo que el filtro por jurisdicción no tiene efecto; fix: leer
args.jurisdictionId y pasar ese filtro al servicio (p. ej. usar
this.casasCunasService.findByJurisdictionId(args.jurisdictionId) si existe, o
this.casasCunasService.findAll({ jurisdictionId: args.jurisdictionId })/filtrar
el array devuelto antes de mapear) y luego construir capacityData solo sobre los
registros filtrados; mantén intactas las propiedades usadas (id, name, province,
canton, district, capacity, currentCount, isVerified).
- Around line 20-33: The module-level variable currentRequestUser causes
cross-request leaks; replace this pattern with an AsyncLocalStorage-based
context: create an AsyncLocalStorage<McpUserContext | null> instance and
implement a runWithCurrentRequestUser(context, fn) helper that calls
als.run(context, fn); refactor setCurrentRequestUser and requireAuth to
read/write the current user via the AsyncLocalStorage (requireAuth should
retrieve the store and throw if null) and remove reliance on the module-scoped
currentRequestUser; finally update the controller to wrap the
transport.handleRequest call inside runWithCurrentRequestUser(...) so each
request has its own isolated context.
- Around line 91-101: The inputSchema currently allows any string for
args.status and then force-casts it to call this.animalsService.findByStatus(),
risking invalid enum values; update validation to enforce AnimalStatus (e.g.,
use z.nativeEnum(AnimalStatus) or validate args.status against the AnimalStatus
enum) and only call this.animalsService.findByStatus(args.status as
AnimalStatus) after the check (otherwise throw a validation error); reference
inputSchema, AnimalStatus, args.status, and animalsService.findByStatus to
locate where to change.

---

Nitpick comments:
In `@apps/backend/src/mcp/mcp.service.spec.ts`:
- Around line 70-170: Las pruebas actualmente llaman los servicios directamente
en vez de invocar las herramientas registradas por McpService; cambia los tests
para inicializar con service.onModuleInit(), obtener el McpServer con
service.getServer() y disparar los handlers registrados (en vez de llamar
animalsService.findAll/findByStatus/subsidiesService.findOne/ etc. directamente)
ya sea invocando los callbacks que McpServer registra vía registerTool() o
enviando una petición de tipo "tools/call" al transport del servidor; usa
setCurrentRequestUser(...) para cada escenario antes de invocar el handler y
luego verifica que los mocks (animalsService, subsidiesService, captureStorage,
casasCunasService, abuseReportsService, jurisdictionsService) hayan sido
llamados con los argumentos esperados.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aab5ed40-91dd-451b-bd0d-e1a914850062

📥 Commits

Reviewing files that changed from the base of the PR and between 53c48af and 3fa41f3.

⛔ Files ignored due to path filters (1)
  • apps/backend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • apps/backend/package.json
  • apps/backend/src/app.module.ts
  • apps/backend/src/mcp/mcp.controller.spec.ts
  • apps/backend/src/mcp/mcp.controller.ts
  • apps/backend/src/mcp/mcp.module.ts
  • apps/backend/src/mcp/mcp.service.spec.ts
  • apps/backend/src/mcp/mcp.service.ts
  • mcp.json

Comment on lines +20 to +33
// Shared state for passing user context to tool handlers
// This is set per-request before the transport handles the message
let currentRequestUser: McpUserContext | null = null;

export function setCurrentRequestUser(user: McpUserContext | null): void {
currentRequestUser = user;
}

function requireAuth(): McpUserContext {
if (!currentRequestUser) {
throw new Error('Authentication required');
}
return currentRequestUser;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd -t f "mcp.service.ts" apps/backend

Repository: altrupets/monorepo

Length of output: 97


🏁 Script executed:

fd -t f "mcp.controller.ts" apps/backend

Repository: altrupets/monorepo

Length of output: 100


🏁 Script executed:

cat -n apps/backend/src/mcp/mcp.service.ts

Repository: altrupets/monorepo

Length of output: 12824


🏁 Script executed:

cat -n apps/backend/src/mcp/mcp.controller.ts

Repository: altrupets/monorepo

Length of output: 4328


No guardes el usuario actual en una variable global compartida.

Ese estado vive a nivel de módulo (línea 22) y apps/backend/src/mcp/mcp.controller.ts lo sobrescribe en línea 60, antes de que transport.handleRequest() (línea 79) ejecute los handlers de las tools de forma asincrónica. Con dos POST /mcp concurrentes, una tool puede evaluar RBAC o persistir reporterId con la identidad de otra solicitud, abriendo una fuga crítica de autorización entre requests.

Usa AsyncLocalStorage para aislar el contexto por cadena de ejecución asincrónica:

🔒 Enfoque de corrección
+import { AsyncLocalStorage } from 'node:async_hooks';
...
-let currentRequestUser: McpUserContext | null = null;
-
-export function setCurrentRequestUser(user: McpUserContext | null): void {
-  currentRequestUser = user;
-}
+const requestUserStorage = new AsyncLocalStorage<McpUserContext>();
+
+export function runWithCurrentRequestUser<T>(
+  user: McpUserContext,
+  callback: () => Promise<T>,
+): Promise<T> {
+  return requestUserStorage.run(user, callback);
+}

 function requireAuth(): McpUserContext {
+  const currentRequestUser = requestUserStorage.getStore();
   if (!currentRequestUser) {
     throw new Error('Authentication required');
   }
   return currentRequestUser;
 }
// apps/backend/src/mcp/mcp.controller.ts (línea ~60)
await runWithCurrentRequestUser(
  { id: user.id, username: user.username, roles: user.roles },
  async () => {
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined,
    });
    await this.mcpService.getServer().connect(transport);
    await transport.handleRequest(req as any, res as any, req.body);
  },
);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Shared state for passing user context to tool handlers
// This is set per-request before the transport handles the message
let currentRequestUser: McpUserContext | null = null;
export function setCurrentRequestUser(user: McpUserContext | null): void {
currentRequestUser = user;
}
function requireAuth(): McpUserContext {
if (!currentRequestUser) {
throw new Error('Authentication required');
}
return currentRequestUser;
}
import { AsyncLocalStorage } from 'node:async_hooks';
// Shared state for passing user context to tool handlers
// Context is isolated per async execution chain using AsyncLocalStorage
const requestUserStorage = new AsyncLocalStorage<McpUserContext>();
export function runWithCurrentRequestUser<T>(
user: McpUserContext,
callback: () => Promise<T>,
): Promise<T> {
return requestUserStorage.run(user, callback);
}
function requireAuth(): McpUserContext {
const currentRequestUser = requestUserStorage.getStore();
if (!currentRequestUser) {
throw new Error('Authentication required');
}
return currentRequestUser;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/mcp/mcp.service.ts` around lines 20 - 33, The module-level
variable currentRequestUser causes cross-request leaks; replace this pattern
with an AsyncLocalStorage-based context: create an
AsyncLocalStorage<McpUserContext | null> instance and implement a
runWithCurrentRequestUser(context, fn) helper that calls als.run(context, fn);
refactor setCurrentRequestUser and requireAuth to read/write the current user
via the AsyncLocalStorage (requireAuth should retrieve the store and throw if
null) and remove reliance on the module-scoped currentRequestUser; finally
update the controller to wrap the transport.handleRequest call inside
runWithCurrentRequestUser(...) so each request has its own isolated context.

Comment on lines +91 to +101
inputSchema: z.object({
species: z.string().optional(),
status: z.string().optional(),
}),
},
async (args) => {
const user = requireAuth();

let animals;
if (args.status) {
animals = await this.animalsService.findByStatus(args.status as any);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the AnimalStatus enum
fd -t f animal.entity.ts apps/backend

Repository: altrupets/monorepo

Length of output: 112


🏁 Script executed:

# Check the mcp.service.ts file to see the current code
head -120 apps/backend/src/mcp/mcp.service.ts | tail -40

Repository: altrupets/monorepo

Length of output: 1211


🏁 Script executed:

# Find the animals.service.ts and check findByStatus() signature
fd -t f animals.service.ts apps/backend

Repository: altrupets/monorepo

Length of output: 105


🏁 Script executed:

cat -n apps/backend/src/animals/entities/animal.entity.ts

Repository: altrupets/monorepo

Length of output: 3363


🏁 Script executed:

cat -n apps/backend/src/animals/animals.service.ts | head -100

Repository: altrupets/monorepo

Length of output: 2476


Validar status con AnimalStatus antes de llamar a findByStatus().

El schema actual acepta cualquier string y luego lo fuerza con as any, pero findByStatus() espera un valor del enum AnimalStatus. Un valor inválido puede causar errores silenciosos en la consulta de base de datos.

Cambio sugerido
+import { AnimalStatus } from '../animals/entities/animal.entity';
...
-          status: z.string().optional(),
+          status: z.enum([AnimalStatus.RESCUED, AnimalStatus.IN_CASA_CUNA, AnimalStatus.ADOPTED, AnimalStatus.RETURNED]).optional(),
...
-          animals = await this.animalsService.findByStatus(args.status as any);
+          animals = await this.animalsService.findByStatus(args.status);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inputSchema: z.object({
species: z.string().optional(),
status: z.string().optional(),
}),
},
async (args) => {
const user = requireAuth();
let animals;
if (args.status) {
animals = await this.animalsService.findByStatus(args.status as any);
inputSchema: z.object({
species: z.string().optional(),
status: z.enum([AnimalStatus.RESCUED, AnimalStatus.IN_CASA_CUNA, AnimalStatus.ADOPTED, AnimalStatus.RETURNED]).optional(),
}),
},
async (args) => {
const user = requireAuth();
let animals;
if (args.status) {
animals = await this.animalsService.findByStatus(args.status);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/mcp/mcp.service.ts` around lines 91 - 101, The inputSchema
currently allows any string for args.status and then force-casts it to call
this.animalsService.findByStatus(), risking invalid enum values; update
validation to enforce AnimalStatus (e.g., use z.nativeEnum(AnimalStatus) or
validate args.status against the AnimalStatus enum) and only call
this.animalsService.findByStatus(args.status as AnimalStatus) after the check
(otherwise throw a validation error); reference inputSchema, AnimalStatus,
args.status, and animalsService.findByStatus to locate where to change.

Comment on lines +302 to +340
// Fetch jurisdiction info
const jurisdiction = await this.jurisdictionsService.findOne(
args.jurisdictionId,
);

// Aggregate KPI data from multiple sources
const casasCunas = await this.casasCunasService.findAll();
const animals = await this.animalsService.findAll();

const totalCapacity = casasCunas.reduce(
(sum, cc) => sum + (cc.capacity ?? 0),
0,
);
const totalOccupied = casasCunas.reduce(
(sum, cc) => sum + (cc.currentCount ?? 0),
0,
);

const kpis = {
jurisdiction: {
id: jurisdiction.id,
name: jurisdiction.name,
},
fosterHomes: {
total: casasCunas.length,
totalCapacity,
totalOccupied,
occupancyRate:
totalCapacity > 0
? Math.round((totalOccupied / totalCapacity) * 100)
: 0,
},
animals: {
total: animals.length,
rescued: animals.filter((a) => a.status === 'RESCUED').length,
inCasaCuna: animals.filter((a) => a.status === 'IN_CASA_CUNA')
.length,
adopted: animals.filter((a) => a.status === 'ADOPTED').length,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Los KPIs municipales se calculan con datos globales.

jurisdictionId sólo se usa para cargar el nombre y el ID de la jurisdicción; la ocupación y los totales salen de casasCunasService.findAll() y animalsService.findAll() sin filtro. Dos municipalidades distintas recibirían los mismos KPIs con distinto encabezado, y además esta agregación siempre carga todo en memoria.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/mcp/mcp.service.ts` around lines 302 - 340, The KPI
aggregation in the method that calls jurisdictionsService.findOne currently uses
casasCunasService.findAll() and animalsService.findAll() (variables casasCunas,
animals, totalCapacity, totalOccupied, kpis) so results are global and loaded
into memory; change those calls to scoped queries that filter by
args.jurisdictionId (e.g., replace findAll() with a service method like
findByJurisdiction(jurisdictionId) or add a filter argument) and, where
possible, replace in-memory reduces with database aggregate/count queries (sum
capacity, sum currentCount, count by status) to compute totalCapacity,
totalOccupied and animals counts without loading all entities into memory.
Ensure the kpis.jurisdiction still uses jurisdictionsService.findOne and that
the fosterHomes and animals totals use the filtered/aggregated results.

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

lapc506 and others added 2 commits March 28, 2026 12:01
Add Model Context Protocol server to the NestJS backend, exposing 6
tools for AI agent integration: search-animals, get-subsidy-status,
list-rescue-requests, get-foster-home-capacity, create-abuse-report,
and get-municipal-kpis. Each tool enforces role-based access control
via JWT authentication on the POST /mcp endpoint using the
StreamableHTTPServerTransport in stateless mode.

Includes unit tests for both McpService (tool registration) and
McpController (auth rejection), fixes a pre-existing syntax error in
app.module.ts (stray semicolon in CacheModule config), and registers
the backend MCP server entry in the root mcp.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…filter by jurisdiction

- get-foster-home-capacity: jurisdictionId parameter now filters casas cunas
  by matching province/canton/district from the jurisdiction entity
- get-municipal-kpis: KPIs now scoped to jurisdiction geography instead of
  querying all data globally; animals filtered by casaCunaId membership

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lapc506 lapc506 force-pushed the ALT-37-backend-mcp-server branch from 8e0dcac to 8f84fdd Compare March 28, 2026 18:01
@lapc506 lapc506 merged commit 4faec53 into main Mar 28, 2026
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@lapc506 lapc506 deleted the ALT-37-backend-mcp-server branch March 28, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant