orquestra-mcp 1.23.1 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/package.json +2 -2
- package/src/api.mjs +13 -0
- package/src/server.mjs +138 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# orquestra-mcp
|
|
2
2
|
|
|
3
|
-
Servidor MCP (Model Context Protocol) para Orquestra. Expone **
|
|
3
|
+
Servidor MCP (Model Context Protocol) para Orquestra. Expone **100 tools**
|
|
4
4
|
que llaman a la API de `orquestra-infra`: lectura de contexto/dashboard/
|
|
5
5
|
workspace/infra/paridad/listados, escritura de tareas/ideas/changelog/
|
|
6
6
|
notas/docs/módulos/flujo/categorías/infra/proyectos, y corridas de QA —
|
|
@@ -111,6 +111,9 @@ Lectura de contexto:
|
|
|
111
111
|
| `list_tasks` | `projectId?`, `statusId?` | Lista de tareas, filtro opcional por estatus |
|
|
112
112
|
| `search` | `projectId?`, `q` | Búsqueda por texto en tareas/notas/ideas/docs |
|
|
113
113
|
| `get_dashboard_summary` | `projectId?` | Hoy/vencidas/bloqueadas, salud de módulos, actividad reciente |
|
|
114
|
+
| `get_project_findings` | `projectId?` | Qué le falta al proyecto, priorizado por severidad (determinístico, sin créditos de IA) |
|
|
115
|
+
| `ask_assistant` | `question`, `history?`, `projectId?` | Pregunta al Asistente sobre el proyecto; responde con propuestas (1 crédito de IA) |
|
|
116
|
+
| `assess_project` | `projectId?` | Resumen y prioridades del proyecto con IA; se reusa 1 h si no cambian los hallazgos |
|
|
114
117
|
| `get_infra_status` | `projectId?` | Servicios, entornos y distribuciones con conteos operational/degraded/down |
|
|
115
118
|
| `get_parity_summary` | `projectId?` | Desfases de paridad multiplataforma (tasks y modules por separado) |
|
|
116
119
|
| `get_workspace_summary` | — | Progreso agregado de todos los proyectos del workspace |
|
|
@@ -192,7 +195,7 @@ create/update sigue siendo criterio del agente y del humano.
|
|
|
192
195
|
|
|
193
196
|
## Estado
|
|
194
197
|
|
|
195
|
-
|
|
198
|
+
100 tools: lectura (contexto, dashboard, workspace, infra, paridad, standup,
|
|
196
199
|
sync report, setup guide, playbooks de etapa y de plataforma, catálogo de
|
|
197
200
|
skills, search) + `list_*` de cada colección + creación/edición de tareas
|
|
198
201
|
(con checklist, tiempo, adjuntos, merge), ideas (con comentarios), notas,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Servidor MCP de Orquestra --
|
|
3
|
+
"version": "1.26.0",
|
|
4
|
+
"description": "Servidor MCP de Orquestra -- 109 tools para leer y escribir tareas, ideas, changelog, infraestructura, contenido, QA y más desde un agente de IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"orquestra-mcp": "bin/orquestra-mcp.mjs"
|
package/src/api.mjs
CHANGED
|
@@ -94,6 +94,19 @@ export async function apiPatch(config, path, { projectId, id, ...body }) {
|
|
|
94
94
|
return parseResponse(res)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// Mismo método que apiPatch, pero para un doc singleton SIN id en la ruta
|
|
98
|
+
// (ej. /v1/content-config) -- set_hourly_rate (8 oct 2026, ORQ-247) es el
|
|
99
|
+
// primer caso real.
|
|
100
|
+
export async function apiPatchPath(config, path, { projectId, ...body }) {
|
|
101
|
+
const url = buildUrl(config, path, { projectId })
|
|
102
|
+
const res = await fetch(url, {
|
|
103
|
+
method: 'PATCH',
|
|
104
|
+
headers: { Authorization: `Bearer ${config.apiToken}`, 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
})
|
|
107
|
+
return parseResponse(res)
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
// Único método DELETE del cliente (21 ago 2026, para delete_checklist_item
|
|
98
111
|
// -- ver CLAUDE.md, "único delete_* real de la API/MCP"). Mismo shape que
|
|
99
112
|
// apiPatch (arma la URL como `${path}/${id}`), sin body -- ningún delete de
|
package/src/server.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import os from 'node:os'
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
5
5
|
import { z } from 'zod'
|
|
6
|
-
import { loadConfig, apiGet, apiPost, apiPatch, apiDelete, putToPresignedUrl, ApiError } from './api.mjs'
|
|
6
|
+
import { loadConfig, apiGet, apiPost, apiPatch, apiPatchPath, apiDelete, putToPresignedUrl, ApiError } from './api.mjs'
|
|
7
7
|
import { STAGES, PLAYBOOKS } from './stages.mjs'
|
|
8
8
|
import { FOUNDATIONS } from './foundations.mjs'
|
|
9
9
|
import { PLATFORM_PLAYBOOKS, TRACKING_TOOLS } from './platformPlaybooks.mjs'
|
|
@@ -63,7 +63,7 @@ const SETUP_GUIDE = {
|
|
|
63
63
|
{ step: 3, section: 'Infraestructura', check: 'list_services / list_environments / list_distributions (o get_infra_status para los 3 juntos)', createOrUpdate: 'create_service|environment|distribution / update_*', matchBy: 'name (o type para servicios/distribuciones)' },
|
|
64
64
|
{ step: 4, section: 'Módulos', check: 'list_modules', createOrUpdate: 'create_module / update_module', matchBy: 'name', dependsOn: ['statusId (paso 1)', 'categoryId (paso 2)'] },
|
|
65
65
|
{ step: 5, section: 'Tareas', check: 'list_tasks', createOrUpdate: 'create_task / update_task', matchBy: 'title', dependsOn: ['statusId', 'categoryId', 'moduleId'] },
|
|
66
|
-
{ step: 6, section: 'Ideas', check: '
|
|
66
|
+
{ step: 6, section: 'Ideas', check: 'list_ideas', createOrUpdate: 'create_idea / update_idea / convert_idea_to_task', matchBy: 'title' },
|
|
67
67
|
{ step: 7, section: 'Notas', check: 'list_notes', createOrUpdate: 'create_note / update_note', matchBy: 'title' },
|
|
68
68
|
{ step: 8, section: 'Changelog', check: 'list_changelog', createOrUpdate: 'create_changelog_entry / update_changelog_entry', matchBy: 'text' },
|
|
69
69
|
{ step: 9, section: 'Docs', check: 'search', createOrUpdate: 'create_doc (sin update_doc todavía)', matchBy: 'title' },
|
|
@@ -117,7 +117,7 @@ No le preguntes al usuario por workspaceId ni projectId -- ya están configurado
|
|
|
117
117
|
|
|
118
118
|
No crees tareas ni entradas de changelog por preguntas triviales o exploración sin cambios reales -- solo por trabajo que de verdad avanza el proyecto.
|
|
119
119
|
|
|
120
|
-
Si estás iniciando una sesión de trabajo real en un proyecto que YA tiene código (no un "vacío" recién conectado, ver arriba) y hace rato no se corrió este chequeo, o el usuario pide explícito "revisa fundamentos"/"qué le falta a este proyecto" (no lo hagas en cada mensaje ni en exploración sin cambios): llamá a get_foundations_checklist y, con acceso real al filesystem del repo (grep de dependencias, archivos de config, workflows de CI -- seguí el detectionHint de cada ítem), confirmá cuáles de verdad faltan -- filtrá por "scales" contra el scale real del proyecto (ver project.scale en get_project_context; un ítem con scales:["platform"] no aplica a una landing). Por cada uno que confirmes ausente con evidencia concreta, llamá a report_foundation_gap con esa evidencia específica -- nunca lo reportes solo porque el checklist lo menciona, sin haber revisado el código de verdad. No repitas un chequeo ya hecho recientemente en la misma sesión.
|
|
120
|
+
Si estás iniciando una sesión de trabajo real en un proyecto que YA tiene código (no un "vacío" recién conectado, ver arriba) y hace rato no se corrió este chequeo, o el usuario pide explícito "revisa fundamentos"/"qué le falta a este proyecto" (no lo hagas en cada mensaje ni en exploración sin cambios): primero llamá a get_project_findings -- ya trae, calculado por el servidor y priorizado, lo que falta o está mal dentro de Orquestra (vencidas, bloqueadas, módulos en riesgo, paridad, infraestructura, servicios que faltan, duplicadas) -- y preséntaselo al usuario por severidad antes de seguir; luego, para lo que solo se ve en el código, llamá a get_foundations_checklist y, con acceso real al filesystem del repo (grep de dependencias, archivos de config, workflows de CI -- seguí el detectionHint de cada ítem), confirmá cuáles de verdad faltan -- filtrá por "scales" contra el scale real del proyecto (ver project.scale en get_project_context; un ítem con scales:["platform"] no aplica a una landing). Por cada uno que confirmes ausente con evidencia concreta, llamá a report_foundation_gap con esa evidencia específica -- nunca lo reportes solo porque el checklist lo menciona, sin haber revisado el código de verdad. No repitas un chequeo ya hecho recientemente en la misma sesión.
|
|
121
121
|
|
|
122
122
|
Si el usuario pregunta explícito qué Claude Skills de terceros le faltan (o pide instalar alguna) -- no lo hagas sin que te lo pidan: llamá a get_skills_catalog y revisá con acceso real al filesystem cuáles de esos keys ya existen en .claude/skills/ (del proyecto) o ~/.claude/skills/ (global). Antes de instalar cualquiera con install_skill, llamá primero a preview_skill y mostrale al usuario el skillMdContent completo -- nunca instales sin que haya visto ese contenido y confirmado explícitamente, los SKILL.md de terceros pueden traer instrucciones que un agente ejecuta solo con cargarlas.`
|
|
123
123
|
|
|
@@ -228,9 +228,29 @@ export function createServer() {
|
|
|
228
228
|
registerRead(
|
|
229
229
|
'search',
|
|
230
230
|
'/v1/search',
|
|
231
|
-
'Busca por texto en tareas, notas, ideas y docs de un proyecto de Orquestra.',
|
|
232
|
-
{
|
|
233
|
-
|
|
231
|
+
'Busca por texto en tareas, notas, ideas y docs de un proyecto de Orquestra. Tope de 30 resultados con las tareas primero: si buscas ideas o docs en un proyecto con muchas tareas, pasa `types` para que no queden tapadas.',
|
|
232
|
+
{
|
|
233
|
+
q: z.string().describe('Texto a buscar'),
|
|
234
|
+
types: z.array(z.enum(['task', 'note', 'idea', 'doc'])).optional().describe('Limitar a estos tipos (opcional). Sin esto busca en los cuatro.'),
|
|
235
|
+
},
|
|
236
|
+
(args, projectId) => ({ projectId, q: args.q, types: args.types?.join(',') })
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
registerRead(
|
|
240
|
+
'list_ideas',
|
|
241
|
+
'/v1/ideas',
|
|
242
|
+
'Lista las ideas de un proyecto de Orquestra. Por default excluye las hechas y descartadas (mismo criterio que list_tasks): pide un `status` puntual (se respeta tal cual, incluso uno cerrado) o includeClosed=true para verlas. Filtros AND por módulo, prioridad y origen.',
|
|
243
|
+
{
|
|
244
|
+
status: z.enum(IDEA_STATUSES).optional().describe('Un estatus puntual (opcional)'),
|
|
245
|
+
includeClosed: z.boolean().optional().describe('true para incluir también hechas y descartadas'),
|
|
246
|
+
moduleId: z.string().optional().describe('ID de módulo para filtrar (opcional)'),
|
|
247
|
+
priority: z.enum(TASK_PRIORITIES).optional().describe('Prioridad para filtrar (opcional)'),
|
|
248
|
+
origin: z.enum(['cliente', 'soporte', 'equipo', 'ventas']).optional().describe('Origen para filtrar (opcional)'),
|
|
249
|
+
},
|
|
250
|
+
(args, projectId) => ({
|
|
251
|
+
projectId, status: args.status, includeClosed: args.includeClosed ? 'true' : undefined,
|
|
252
|
+
moduleId: args.moduleId, priority: args.priority, origin: args.origin,
|
|
253
|
+
})
|
|
234
254
|
)
|
|
235
255
|
|
|
236
256
|
// ═══ LECTURA (nivel A -- dashboard, workspace, infra, paridad, playbooks) ═══
|
|
@@ -243,6 +263,31 @@ export function createServer() {
|
|
|
243
263
|
(args, projectId) => ({ projectId })
|
|
244
264
|
)
|
|
245
265
|
|
|
266
|
+
registerRead(
|
|
267
|
+
'get_project_findings',
|
|
268
|
+
'/v1/findings',
|
|
269
|
+
'Qué le falta o qué está mal en un proyecto, ya calculado por el servidor y priorizado por severidad (high/medium/low): tareas vencidas o bloqueadas, módulos en riesgo, tareas sin módulo/fecha, referencias rotas, desfases de paridad, infraestructura caída, servicios que faltan (backend/base de datos/auth/analytics/push), posibles tareas duplicadas y fundamentos olvidados que ya se reportaron. Cada hallazgo trae título, detalle, entidades afectadas (con id) y una acción sugerida. Es determinístico, rápido y no consume créditos de IA. Lo ya descartado o aceptado como sugerencia no aparece.',
|
|
270
|
+
{},
|
|
271
|
+
(args, projectId) => ({ projectId })
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
registerCreate(
|
|
275
|
+
'ask_assistant',
|
|
276
|
+
'/v1/assistant/ask',
|
|
277
|
+
'Pregúntale al Asistente de Orquestra sobre el proyecto (qué hacer hoy, qué está en riesgo, por qué algo está atorado). El servidor arma el contexto real del proyecto -- tareas abiertas, hallazgos, módulos -- y responde { answer, proposals } citando tareas por código. Consume 1 crédito de IA del workspace por pregunta; si solo necesitas saber qué falta, usa get_project_findings, que es gratis. Solo recomienda: no cambia nada en el proyecto.',
|
|
278
|
+
{
|
|
279
|
+
question: z.string().describe('La pregunta, en lenguaje natural (máx. 1000 caracteres)'),
|
|
280
|
+
history: z.array(z.object({ role: z.enum(['user', 'assistant']), text: z.string() })).optional().describe('Turnos previos de la conversación, si hay'),
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
registerCreate(
|
|
285
|
+
'assess_project',
|
|
286
|
+
'/v1/assistant/assess',
|
|
287
|
+
'Diagnóstico corto del proyecto con IA: un resumen del estado y de 1 a 3 prioridades, partiendo de los hallazgos de get_project_findings. El servidor lo guarda 1 hora mientras los hallazgos no cambien, así que repetirlo es gratis; solo cobra 1 crédito de IA cuando lo recalcula. Si no hay hallazgos no usa IA.',
|
|
288
|
+
{}
|
|
289
|
+
)
|
|
290
|
+
|
|
246
291
|
registerRead(
|
|
247
292
|
'get_infra_status',
|
|
248
293
|
'/v1/infra',
|
|
@@ -290,6 +335,54 @@ export function createServer() {
|
|
|
290
335
|
}
|
|
291
336
|
)
|
|
292
337
|
|
|
338
|
+
// Config "suelta" que hasta el 8 oct 2026 solo se podía tocar desde la
|
|
339
|
+
// web (ver workspaceSettings.mjs en orquestra-infra) -- José pidió poder
|
|
340
|
+
// darla de alta también por acá.
|
|
341
|
+
server.registerTool(
|
|
342
|
+
'set_slack_webhook',
|
|
343
|
+
{
|
|
344
|
+
description: 'Configura (o quita, con url:"") el webhook de Slack del workspace -- cada notificación (tareas asignadas, QA, infra, contenido...) también se manda ahí, además de in-app/push/correo. Requiere un token de servicio SIN projectId (mismo criterio que get_workspace_summary/init_project) -- un token scopeado a un proyecto puntual no puede tocar esto.',
|
|
345
|
+
inputSchema: {
|
|
346
|
+
url: z.string().describe('URL del Incoming Webhook de Slack (https://hooks.slack.com/services/...), o "" para desactivarlo'),
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
async ({ url }) => {
|
|
350
|
+
try {
|
|
351
|
+
const data = await apiPatch(config, '/v1/workspaces', { id: config.workspaceId, slackWebhookUrl: url })
|
|
352
|
+
return textResult(data)
|
|
353
|
+
} catch (err) {
|
|
354
|
+
return errorResult(err)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
registerRead('get_content_budget', '/v1/content-config',
|
|
360
|
+
'Lee los topes numéricos de presupuesto/tarifa de Inversión de Contenido de un proyecto (hourlyRate, monthlyBudget, dailyCap, perCampaignCap, alertThresholdPct). No trae el resto de contentConfig (guía de marca, imágenes, fragmentos aprobados) -- eso es contenido rico pensado para autoría humana en la web.',
|
|
361
|
+
{}, (args, projectId) => ({ projectId }))
|
|
362
|
+
|
|
363
|
+
server.registerTool(
|
|
364
|
+
'set_hourly_rate',
|
|
365
|
+
{
|
|
366
|
+
description: 'Configura la tarifa por hora (u otros topes numéricos de presupuesto de Inversión de Contenido) de un proyecto -- alimenta "Horas del equipo" (Σ timeEntries del mes en curso × esta tarifa) y el reporte de horas facturables de la web (sección Horas). Al menos un campo es obligatorio.',
|
|
367
|
+
inputSchema: {
|
|
368
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
369
|
+
hourlyRate: z.number().optional().describe('Tarifa por hora única del proyecto (misma moneda que el resto de Inversión)'),
|
|
370
|
+
monthlyBudget: z.number().optional().describe('Presupuesto asignado del mes'),
|
|
371
|
+
dailyCap: z.number().optional().describe('Tope diario -- informativo, no se aplica solo'),
|
|
372
|
+
perCampaignCap: z.number().optional().describe('Tope por campaña -- sí se aplica solo (pausa la campaña al llegar acá)'),
|
|
373
|
+
alertThresholdPct: z.number().optional().describe('Default 80 -- aviso cuando el gasto llega a este % del monthlyBudget'),
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
async ({ projectId, ...body }) => {
|
|
377
|
+
try {
|
|
378
|
+
const data = await apiPatchPath(config, '/v1/content-config', { projectId: resolveProjectId(projectId), ...body })
|
|
379
|
+
return textResult(data)
|
|
380
|
+
} catch (err) {
|
|
381
|
+
return errorResult(err)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
|
|
293
386
|
registerRead(
|
|
294
387
|
'list_modules',
|
|
295
388
|
'/v1/modules',
|
|
@@ -516,7 +609,8 @@ export function createServer() {
|
|
|
516
609
|
scope: z.array(z.object({ distId: z.string(), layer: z.string().optional(), done: z.boolean() })).optional()
|
|
517
610
|
.describe('Alcance por distribución (paridad multiplataforma) -- para setearlo desde la creación en vez de crear y después update_task. Usa get_parity_summary/list_distributions para ver qué distId existen. Si una distId tiene hermanas (mismo productKey), el server suma las que falten solo, con done:false -- la respuesta trae parityAddedDistIds con lo que agregó: avísale al usuario que la tarea también quedó pendiente en esas plataformas.'),
|
|
518
611
|
skipParity: z.boolean().optional().describe('true = NO sumar las plataformas hermanas al scope aunque tengan el mismo productKey. Solo cuando el trabajo es genuinamente de UNA plataforma (ej. un ajuste de Gradle que no tiene contraparte en iOS) -- no lo mandes por default. Las tareas type="bug" nunca propagan, sin necesidad de esto.'),
|
|
519
|
-
relatedTaskId: z.string().optional().describe('"Bloqueada por" -- id de otra tarea.
|
|
612
|
+
relatedTaskId: z.string().optional().describe('"Bloqueada por" -- id de otra tarea. LEGACY desde ORQ-248: si vas a bloquear por más de una tarea, usa blockedByIds en vez de esto (relatedTaskId se sigue llenando solo, como blockedByIds[0], para paridad con iOS/Android).'),
|
|
613
|
+
blockedByIds: z.array(z.string()).optional().describe('"Bloqueada por" -- ids de otras tareas (puede ser más de una). Esta tarea se muestra como bloqueada mientras ALGUNA de esas no llegue a un estatus final (isFinal). Reemplaza a relatedTaskId para bloqueos múltiples -- preferí este campo, no relatedTaskId, salvo que de verdad sea un solo bloqueador.'),
|
|
520
614
|
location: z.string().optional().describe('Solo relevante para type "meeting"/"call" (plataforma o lugar, ej. "Zoom", "Oficina") o "followup"/"business" (contexto libre) -- ignóralo para el resto de los tipos.'),
|
|
521
615
|
estimatedDays: z.number().optional().describe('Estimación en días de trabajo.'),
|
|
522
616
|
stack: z.array(z.string()).optional().describe('Tecnologías involucradas (mismo vocabulario que el stack de una distribución, ej. "node", "react", "postgres").'),
|
|
@@ -551,7 +645,8 @@ export function createServer() {
|
|
|
551
645
|
tags: z.array(z.string()).optional(),
|
|
552
646
|
scope: z.array(z.object({ distId: z.string(), layer: z.string().optional(), done: z.boolean() })).optional()
|
|
553
647
|
.describe('Alcance por distribución -- usa get_parity_summary para ver el scope actual antes de editarlo'),
|
|
554
|
-
relatedTaskId: z.string().optional().describe('"Bloqueada por" -- id de otra tarea, o "" para quitar el bloqueo.
|
|
648
|
+
relatedTaskId: z.string().optional().describe('"Bloqueada por" -- id de otra tarea, o "" para quitar el bloqueo. LEGACY, ver create_task -- preferí blockedByIds.'),
|
|
649
|
+
blockedByIds: z.array(z.string()).optional().describe('"Bloqueada por" -- reemplaza el array COMPLETO (no hace merge), [] quita todos los bloqueos. Ver create_task.'),
|
|
555
650
|
location: z.string().optional().describe('Solo relevante para type "meeting"/"call"/"followup"/"business". Ver create_task.'),
|
|
556
651
|
comment: z.string().optional().describe('Explica qué hiciste o por qué cambia la tarea -- queda como comentario visible en ella. Obligatorio en la práctica cuando mandas statusId: sin esto, alguien ve que la tarea cambió de estatus pero no por qué.'),
|
|
557
652
|
estimatedDays: z.number().optional().describe('Estimación en días de trabajo.'),
|
|
@@ -1652,7 +1747,7 @@ export function createServer() {
|
|
|
1652
1747
|
deployMode: z.enum(['auto', 'manual']).optional().describe('Modo de deploy -- web'),
|
|
1653
1748
|
hasPush: z.boolean().optional().describe('Tiene push notifications -- apps (ios/android)'),
|
|
1654
1749
|
storeUrl: z.string().optional(),
|
|
1655
|
-
stack: z.array(z.string()).optional(),
|
|
1750
|
+
stack: z.array(z.string()).optional().describe('Tecnologías de la plataforma. Puedes mandar nombres ("Kotlin", "Jetpack Compose", "Node.js"): la API los normaliza a las claves de su catálogo (schema/enums/TechStack.json en orquestra-infra) y conserva lo que no reconoce.'),
|
|
1656
1751
|
version: z.string().optional().describe('Versión en store'),
|
|
1657
1752
|
reviewVersion: z.string().optional().describe('Build en revisión -- apps'),
|
|
1658
1753
|
repoUrl: z.string().optional().describe('URL del repo de este código (ej. de "git remote get-url origin") -- solo informativo, no requiere credenciales. Distinto de conectar el repo con PAT (eso es Workspace → Conexiones, un paso deliberado del usuario, nunca lo hagas solo).'),
|
|
@@ -1673,7 +1768,7 @@ export function createServer() {
|
|
|
1673
1768
|
deployMode: z.enum(['auto', 'manual']).optional(),
|
|
1674
1769
|
hasPush: z.boolean().optional(),
|
|
1675
1770
|
storeUrl: z.string().optional(),
|
|
1676
|
-
stack: z.array(z.string()).optional(),
|
|
1771
|
+
stack: z.array(z.string()).optional().describe('Tecnologías de la plataforma. Puedes mandar nombres ("Kotlin", "Jetpack Compose", "Node.js"): la API los normaliza a las claves de su catálogo (schema/enums/TechStack.json en orquestra-infra) y conserva lo que no reconoce.'),
|
|
1677
1772
|
version: z.string().optional(),
|
|
1678
1773
|
reviewVersion: z.string().optional(),
|
|
1679
1774
|
repoUrl: z.string().optional().describe('URL del repo de este código -- solo informativo, no requiere credenciales.'),
|
|
@@ -1763,6 +1858,39 @@ export function createServer() {
|
|
|
1763
1858
|
status: z.enum(CAMPAIGN_STATUSES).optional(),
|
|
1764
1859
|
})
|
|
1765
1860
|
|
|
1861
|
+
// Registro de riesgos a nivel de proyecto (ORQ-246, 8 oct 2026) --
|
|
1862
|
+
// distinto de "bloqueada" en una tarea puntual (relatedTaskId/
|
|
1863
|
+
// isTaskBlocked): esto es "qué puede hacer fallar el proyecto" en
|
|
1864
|
+
// general, sin necesitar estar ligado a una tarea.
|
|
1865
|
+
const RISK_LEVELS = ['low', 'medium', 'high']
|
|
1866
|
+
const RISK_STATUSES = ['open', 'mitigating', 'closed']
|
|
1867
|
+
|
|
1868
|
+
registerRead('list_risks', '/v1/risks',
|
|
1869
|
+
'Lista los riesgos/bloqueos a nivel de proyecto (dependencias externas, decisiones pendientes de un cliente, etc. -- no ligados necesariamente a una tarea puntual). Úsala antes de create_risk para no duplicar.',
|
|
1870
|
+
{}, (args, projectId) => ({ projectId }))
|
|
1871
|
+
|
|
1872
|
+
registerCreate('create_risk', '/v1/risks', 'Registra un riesgo/bloqueo a nivel de proyecto -- algo que puede hacer fallar o atrasar el proyecto en general, no el bloqueo puntual de una tarea (para eso usa relatedTaskId en update_task).', {
|
|
1873
|
+
title: z.string(),
|
|
1874
|
+
description: z.string().optional(),
|
|
1875
|
+
probability: z.enum(RISK_LEVELS).optional().describe('Default: medium'),
|
|
1876
|
+
impact: z.enum(RISK_LEVELS).optional().describe('Default: medium'),
|
|
1877
|
+
mitigation: z.string().optional().describe('Plan o texto libre de qué se hace para reducir este riesgo'),
|
|
1878
|
+
status: z.enum(RISK_STATUSES).optional().describe('Default: open. "closed" en vez de borrar -- nunca se borra un riesgo.'),
|
|
1879
|
+
ownerUid: z.string().optional().describe('Dueño del riesgo -- le avisa solo a esa persona al asignarla (ver list de miembros del workspace)'),
|
|
1880
|
+
moduleId: z.string().optional().describe('Módulo relacionado, si aplica (ver list_modules)'),
|
|
1881
|
+
})
|
|
1882
|
+
|
|
1883
|
+
registerUpdate('update_risk', '/v1/risks', 'Edita un riesgo existente -- típicamente para actualizar su mitigación o cerrarlo (status: "closed") cuando deja de aplicar.', {
|
|
1884
|
+
title: z.string().optional(),
|
|
1885
|
+
description: z.string().optional(),
|
|
1886
|
+
probability: z.enum(RISK_LEVELS).optional(),
|
|
1887
|
+
impact: z.enum(RISK_LEVELS).optional(),
|
|
1888
|
+
mitigation: z.string().optional(),
|
|
1889
|
+
status: z.enum(RISK_STATUSES).optional(),
|
|
1890
|
+
ownerUid: z.string().optional(),
|
|
1891
|
+
moduleId: z.string().optional(),
|
|
1892
|
+
})
|
|
1893
|
+
|
|
1766
1894
|
registerRead('list_content', '/v1/content',
|
|
1767
1895
|
'Lista las publicaciones de contenido (posts programados/publicados) de un proyecto.',
|
|
1768
1896
|
{}, (args, projectId) => ({ projectId }))
|