orquestra-mcp 1.32.0 → 1.33.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/package.json +1 -1
- package/src/server.mjs +107 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "Servidor MCP de Orquestra -- 115 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": {
|
package/src/server.mjs
CHANGED
|
@@ -26,6 +26,10 @@ function textResult(data) {
|
|
|
26
26
|
const PROJECT_ID_DESC = 'ID del proyecto dentro del workspace configurado. Si hay un proyecto default configurado (via "setup" o ORQUESTRA_DEFAULT_PROJECT_ID), este argumento se ignora -- el server queda fijo a ese proyecto y no puede leer/escribir otro.'
|
|
27
27
|
|
|
28
28
|
const TASK_TYPES = ['feature', 'bug', 'improvement', 'tech', 'docs', 'idea', 'design', 'security', 'meeting', 'call', 'followup', 'business']
|
|
29
|
+
// Actividades (ORQ-295): reunión/llamada/seguimiento. 'business' sigue siendo tarea (entregable). Mismas listas que
|
|
30
|
+
// orquestra-infra/functions/api/lib/activities.mjs y schema/enums/ActivityStatus.json.
|
|
31
|
+
const ACTIVITY_TYPES = ['meeting', 'call', 'followup']
|
|
32
|
+
const ACTIVITY_STATUSES = ['scheduled', 'done', 'cancelled']
|
|
29
33
|
const TASK_PRIORITIES = ['high', 'medium', 'low']
|
|
30
34
|
const CHANGELOG_SECTIONS = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security']
|
|
31
35
|
// 'postmortem' faltaba acá (gap preexistente, encontrado 12 ago 2026) --
|
|
@@ -95,8 +99,10 @@ Analítica y anuncios en un sitio web: antes de instalar o tocar Google Analytic
|
|
|
95
99
|
|
|
96
100
|
Durante el trabajo normal de código, sin que el usuario te lo pida explícitamente y SIN ESPERAR a que lo mencione (si tienes esta tool disponible en la sesión, se usa por default en todo trabajo real, no solo cuando alguien dice "usa Orquestra"):
|
|
97
101
|
- Si te pide una funcionalidad o arreglo real (no una pregunta rápida ni exploración de código sin cambios), revisa con list_tasks/search si ya existe una tarea relacionada; si no, créala con create_task ANTES de empezar a trabajar, en el estatus inicial del proyecto (ver list_statuses). Si no hay un proyecto default configurado para esta carpeta, no te rindas ahí -- usa get_workspace_summary para encontrar el proyecto correcto y pásalo explícito como projectId en cada llamada.
|
|
102
|
+
- Orquestra es el ÚNICO lugar donde se planea y se lleva el avance del trabajo: el plan de pasos va como checklist de la tarea (add_checklist_item) y se marca con update_checklist_item conforme terminas cada uno. No lleves una lista de pendientes paralela en el chat, en archivos temporales ni en la herramienta de tareas de tu propio cliente -- se pierde al cerrar la sesión y el usuario no la ve. Si retomas trabajo en una sesión nueva, parte de la tarea y sus ítems pendientes (list_tasks + list_checklist_items), no de cero. Excepción: un cambio de un solo paso (renombrar algo, responder una pregunta) no necesita tarea.
|
|
98
103
|
- Mientras trabajas, mueve la tarea por los estatus reales según avanzas (no la dejes en el inicial hasta el final). Al terminar, muévela a su estatus final con update_task y registra una entrada de changelog con create_changelog_entry -- así el usuario nunca tiene que documentar esto a mano. Si sabes en qué repo real estás trabajando (podés correr "git remote get-url origin" o ya lo sabes por la carpeta), pásalo SIEMPRE como "repo" ("owner/name") -- encontrado real (17 ago 2026): ni el hook local ni el GitHub Action ni la reconciliación de orquestra-infra lo estaban mandando pese a que el campo existe hace rato, así que ninguna entrada automática sabía de qué repo/plataforma venía. Si además la distribución de ese repo ya está identificada (ver list_distributions), pásala en "distributions" -- sin esto el changelog no puede filtrar/agrupar por plataforma.
|
|
99
104
|
- Todo cambio de statusId vía update_task valida la transición (nextIds) y queda en el historial de la tarea -- pero ese historial no dice nada por sí solo. Manda SIEMPRE el parámetro "comment" cuando cambies statusId, explicando qué hiciste o por qué -- sin esto, el usuario ve que la tarea cambió pero no sabe por qué ni qué se hizo de verdad (encontrado real: quedaba invisible, a diferencia de cuando un humano mueve la tarjeta a mano en el kanban).
|
|
105
|
+
- Reuniones, llamadas y seguimientos NO son tareas: son actividades (no se entregan, se programan y se registran). Usa log_activity para agendarlas o registrarlas, update_activity para marcarlas hechas con su resultado (outcome) y siguiente paso (nextStep), y list_activities para ver la agenda/bitácora -- nunca create_task con esos tipos. Si del siguiente paso sale trabajo real, eso sí es una tarea (create_task normal, con módulo y fecha).
|
|
100
106
|
- Antes de crear un módulo/estatus/categoría/servicio/entorno/distribución, revisa con el list_* correspondiente que no exista ya uno con ese nombre.
|
|
101
107
|
- Si una nota que vas a crear (create_note) es claramente sobre un módulo o una tarea específica -- por ejemplo durante un setup guiado donde acabas de crear ambos -- mándale moduleId/taskId para ligarla, en vez de dejarla suelta. No inventes la relación si no es clara.
|
|
102
108
|
- create_changelog_entry SIEMPRE deja la entrada en Unreleased (nunca mandes version salvo que el usuario te haya dado un número real) -- cortar una versión es release_changelog_version, una acción DELIBERADA que solo se llama si el usuario la pide explícitamente ("cortá la versión", "hacé el release de X") o confirma un número/fecha real que vos propusiste. No la dispares solo porque terminaste una tarea o un lote de trabajo grande.
|
|
@@ -214,7 +220,7 @@ export function createServer() {
|
|
|
214
220
|
registerRead(
|
|
215
221
|
'list_tasks',
|
|
216
222
|
'/v1/tasks',
|
|
217
|
-
'Lista las tareas de un proyecto de Orquestra, opcionalmente filtradas por estatus/módulo/tipo/prioridad/responsable/tag. Los filtros se combinan con AND -- pásalos en vez de traer todo el proyecto y filtrar tú mismo del lado del cliente. Por default excluye tareas cerradas (isFinal/isCancel/isArchive) -- un proyecto con historial acumula cientos, traerlas todas revienta el contexto sin que nadie las haya pedido. Si mandas statusId puntual, eso ya es una elección explícita y no se le suma este filtro (podés pedir "solo Listo"). Para historial/auditoría real, usa includeClosed=true.',
|
|
223
|
+
'Lista las tareas de un proyecto de Orquestra, opcionalmente filtradas por estatus/módulo/tipo/prioridad/responsable/tag. Los filtros se combinan con AND -- pásalos en vez de traer todo el proyecto y filtrar tú mismo del lado del cliente. Por default excluye tareas cerradas (isFinal/isCancel/isArchive) -- un proyecto con historial acumula cientos, traerlas todas revienta el contexto sin que nadie las haya pedido. Si mandas statusId puntual, eso ya es una elección explícita y no se le suma este filtro (podés pedir "solo Listo"). Para historial/auditoría real, usa includeClosed=true. No incluye actividades (reuniones/llamadas/seguimientos) -- esas van con list_activities.',
|
|
218
224
|
{
|
|
219
225
|
statusId: z.string().optional().describe('ID de estatus para filtrar (opcional) -- pedir un estatus puntual (incluso uno cerrado) siempre se respeta tal cual, sin el filtro default de abiertas'),
|
|
220
226
|
moduleId: z.string().optional().describe('ID de módulo para filtrar (opcional)'),
|
|
@@ -239,10 +245,10 @@ export function createServer() {
|
|
|
239
245
|
registerRead(
|
|
240
246
|
'search',
|
|
241
247
|
'/v1/search',
|
|
242
|
-
'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.',
|
|
248
|
+
'Busca por texto en tareas, actividades (reuniones/llamadas), 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.',
|
|
243
249
|
{
|
|
244
250
|
q: z.string().describe('Texto a buscar'),
|
|
245
|
-
types: z.array(z.enum(['task', 'note', 'idea', 'doc'])).optional().describe('Limitar a estos tipos (opcional). Sin esto busca en
|
|
251
|
+
types: z.array(z.enum(['task', 'activity', 'note', 'idea', 'doc'])).optional().describe('Limitar a estos tipos (opcional). Sin esto busca en todos.'),
|
|
246
252
|
},
|
|
247
253
|
(args, projectId) => ({ projectId, q: args.q, types: args.types?.join(',') })
|
|
248
254
|
)
|
|
@@ -604,7 +610,7 @@ export function createServer() {
|
|
|
604
610
|
registerCreate(
|
|
605
611
|
'create_task',
|
|
606
612
|
'/v1/tasks',
|
|
607
|
-
'Crea una tarea en un proyecto de Orquestra. moduleId y dueDate son OBLIGATORIOS -- revisa list_modules primero (si ninguno aplica, creá uno con create_module antes de crear la tarea, nunca la dejes sin agrupar); dueDate se calcula con criterio real (prioridad/tamaño del trabajo), nunca un valor arbitrario.',
|
|
613
|
+
'Crea una tarea en un proyecto de Orquestra. moduleId y dueDate son OBLIGATORIOS -- revisa list_modules primero (si ninguno aplica, creá uno con create_module antes de crear la tarea, nunca la dejes sin agrupar); dueDate se calcula con criterio real (prioridad/tamaño del trabajo), nunca un valor arbitrario. Una reunión, llamada o seguimiento NO es tarea: usa log_activity (type "business" sí es tarea: entregable no-código).',
|
|
608
614
|
{
|
|
609
615
|
title: z.string().describe('Título de la tarea'),
|
|
610
616
|
description: z.string().optional(),
|
|
@@ -679,6 +685,103 @@ export function createServer() {
|
|
|
679
685
|
}
|
|
680
686
|
)
|
|
681
687
|
|
|
688
|
+
// ═══ ACTIVIDADES (ORQ-295) ═══════════════════════════════════════════════════
|
|
689
|
+
// Reunión/llamada/seguimiento: misma colección que las tareas en la
|
|
690
|
+
// API (POST/PATCH /v1/tasks con type de actividad), pero sin módulo
|
|
691
|
+
// obligatorio, fuera del kanban y con su propio ciclo (activityStatus). La
|
|
692
|
+
// hora se pasa a scheduledAt ACÁ, en la máquina del usuario, porque la API
|
|
693
|
+
// no conoce su zona horaria (mismo motivo que set_task_reminder).
|
|
694
|
+
const toScheduledAt = (date, time) => {
|
|
695
|
+
if (!time) return undefined
|
|
696
|
+
const ms = new Date(`${date}T${time}:00`).getTime()
|
|
697
|
+
if (Number.isNaN(ms)) throw new Error(`Fecha/hora inválida: ${date} ${time}`)
|
|
698
|
+
return ms
|
|
699
|
+
}
|
|
700
|
+
const ACTIVITY_FIELDS_SCHEMA = {
|
|
701
|
+
location: z.string().optional().describe('Plataforma o lugar ("Zoom", "Oficina del cliente") o contexto libre para un seguimiento.'),
|
|
702
|
+
participants: z.array(z.string()).optional().describe('Nombres libres de quienes participan (cliente, equipo) -- no uids.'),
|
|
703
|
+
description: z.string().optional().describe('Agenda o notas previas.'),
|
|
704
|
+
outcome: z.string().optional().describe('Resultado / minuta corta -- qué se acordó. Llénalo al marcarla hecha.'),
|
|
705
|
+
nextStep: z.string().optional().describe('Siguiente paso acordado. Si es trabajo real, además crea la tarea con create_task y liga su id en nextStepTaskId.'),
|
|
706
|
+
nextStepTaskId: z.string().optional().describe('id de la tarea creada desde nextStep.'),
|
|
707
|
+
moduleId: z.string().optional().describe('Opcional -- a diferencia de las tareas, una actividad no necesita módulo.'),
|
|
708
|
+
assignedTo: z.string().optional().describe('uid del responsable'),
|
|
709
|
+
recurrence: z.object({ freq: z.enum(['daily', 'weekly', 'monthly']) }).nullable().optional()
|
|
710
|
+
.describe('Repetición (ej. junta semanal): al marcarla hecha se agenda sola la siguiente, misma hora y participantes. null la quita.'),
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
server.registerTool(
|
|
714
|
+
'log_activity',
|
|
715
|
+
{
|
|
716
|
+
description: 'Agenda o registra una actividad -- reunión, llamada o seguimiento -- en un proyecto de Orquestra. No es una tarea: no lleva módulo obligatorio ni pasa por el kanban; aparece en la Agenda y en el calendario. Para una que YA pasó, manda activityStatus "done" con outcome (y nextStep si hubo). Si el siguiente paso es trabajo real, crea además la tarea con create_task.',
|
|
717
|
+
inputSchema: {
|
|
718
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
719
|
+
type: z.enum(ACTIVITY_TYPES).describe('meeting = reunión, call = llamada, followup = seguimiento'),
|
|
720
|
+
title: z.string().describe('Título de la actividad'),
|
|
721
|
+
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').describe('Día de la actividad (YYYY-MM-DD, hora local del usuario).'),
|
|
722
|
+
time: z.string().regex(/^\d{2}:\d{2}$/, 'Formato esperado: HH:MM').optional().describe('Hora local (HH:MM, 24 h), si se sabe.'),
|
|
723
|
+
activityStatus: z.enum(ACTIVITY_STATUSES).optional().describe('Default "scheduled" (programada). "done" para registrar una que ya pasó.'),
|
|
724
|
+
...ACTIVITY_FIELDS_SCHEMA,
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
async ({ projectId, date, time, ...body }) => {
|
|
728
|
+
try {
|
|
729
|
+
const scheduledAt = toScheduledAt(date, time)
|
|
730
|
+
const data = await apiPost(config, '/v1/tasks', {
|
|
731
|
+
projectId: resolveProjectId(projectId), ...body, dueDate: date, ...(scheduledAt ? { scheduledAt } : {}),
|
|
732
|
+
})
|
|
733
|
+
return textResult(data)
|
|
734
|
+
} catch (err) {
|
|
735
|
+
return errorResult(err)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
server.registerTool(
|
|
741
|
+
'update_activity',
|
|
742
|
+
{
|
|
743
|
+
description: 'Edita una actividad (reunión/llamada/seguimiento) de Orquestra: reprogramarla, marcarla hecha con su resultado (activityStatus "done" + outcome + nextStep) o cancelarla. El server mueve solo el estatus interno; no hay que validar transiciones del kanban.',
|
|
744
|
+
inputSchema: {
|
|
745
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
746
|
+
id: z.string().describe('ID de la actividad (ver list_activities)'),
|
|
747
|
+
title: z.string().optional(),
|
|
748
|
+
type: z.enum(ACTIVITY_TYPES).optional(),
|
|
749
|
+
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').optional().describe('Nuevo día (YYYY-MM-DD). Si cambias el día y había hora, manda también time.'),
|
|
750
|
+
time: z.string().regex(/^\d{2}:\d{2}$/, 'Formato esperado: HH:MM').optional().describe('Nueva hora local (HH:MM). Requiere date.'),
|
|
751
|
+
activityStatus: z.enum(ACTIVITY_STATUSES).optional(),
|
|
752
|
+
...ACTIVITY_FIELDS_SCHEMA,
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
async ({ projectId, id, date, time, ...body }) => {
|
|
756
|
+
try {
|
|
757
|
+
if (time && !date) throw new Error('Para cambiar la hora manda también date (YYYY-MM-DD).')
|
|
758
|
+
const scheduledAt = date ? toScheduledAt(date, time) : undefined
|
|
759
|
+
const data = await apiPatch(config, '/v1/tasks', {
|
|
760
|
+
projectId: resolveProjectId(projectId), id, ...body,
|
|
761
|
+
...(date ? { dueDate: date } : {}), ...(scheduledAt ? { scheduledAt } : {}),
|
|
762
|
+
})
|
|
763
|
+
return textResult(data)
|
|
764
|
+
} catch (err) {
|
|
765
|
+
return errorResult(err)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
registerRead(
|
|
771
|
+
'list_activities',
|
|
772
|
+
'/v1/activities',
|
|
773
|
+
'Lista las actividades (reuniones, llamadas, seguimientos) de un proyecto de Orquestra. status "scheduled" = agenda (próximas primero), "done" = bitácora (más recientes primero); sin status trae todas. from/to acotan por día.',
|
|
774
|
+
{
|
|
775
|
+
status: z.enum(ACTIVITY_STATUSES).optional(),
|
|
776
|
+
type: z.enum(ACTIVITY_TYPES).optional(),
|
|
777
|
+
moduleId: z.string().optional(),
|
|
778
|
+
from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe('Desde (YYYY-MM-DD, inclusive)'),
|
|
779
|
+
to: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe('Hasta (YYYY-MM-DD, inclusive)'),
|
|
780
|
+
limit: z.number().optional().describe('Default 100'),
|
|
781
|
+
},
|
|
782
|
+
(args, projectId) => ({ projectId, status: args.status, type: args.type, moduleId: args.moduleId, from: args.from, to: args.to, limit: args.limit })
|
|
783
|
+
)
|
|
784
|
+
|
|
682
785
|
// Fusionar tareas (20 ago 2026) -- registradas directo con
|
|
683
786
|
// server.registerTool (no registerCreate/registerUpdate) porque son
|
|
684
787
|
// POST a `${path}/${id}/merge|unmerge`, no un CRUD estándar. Fusión
|