orquestra-mcp 1.61.0 → 1.70.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 +13 -2
- package/package.json +2 -2
- package/src/api.mjs +6 -2
- package/src/closePath.mjs +65 -0
- package/src/server.mjs +675 -119
- package/src/toolGuides.mjs +46 -0
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 **161 tools**
|
|
4
4
|
(conteo al 24 oct 2026, v1.49.0; la lista real está en `src/server.mjs`)
|
|
5
5
|
que llaman a la API de `orquestra-infra`: lectura de contexto/dashboard/
|
|
6
6
|
workspace/infra/paridad/listados, escritura de tareas/ideas/changelog/
|
|
@@ -141,6 +141,17 @@ claude mcp add orquestra \
|
|
|
141
141
|
-- npx -y orquestra-mcp@latest
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
## Novedades 1.66.0 (ORQ-532..535)
|
|
145
|
+
|
|
146
|
+
- `close_task({id, comment, completeChecklist?, waiveVerifications?})`: cierra una tarea desde cualquier estatus
|
|
147
|
+
recorriendo el flujo (camino más corto por `nextIds` hasta el final, pasando por "En revisión"); un solo
|
|
148
|
+
`comment` (en el último paso). Respeta las guardas de checklist/verificaciones.
|
|
149
|
+
- `get_tool_guide({topic})`: reglas completas de `update_task`, `create_task`, `update_tasks`, `close_task` y
|
|
150
|
+
`related_impact`; las descripciones de esas tools son cortas a propósito (test de tope en `test/mejorasMcp.test.mjs`).
|
|
151
|
+
- `update_tasks` acepta `compact:true` (recomendado): cada resultado trae solo `{ index, id, ok, status, shortCode, statusId, relatedImpact?, unresolvedLinks?, error? }`.
|
|
152
|
+
- `create_task` acepta `checklist: [{text, done?, assignedTo?}]` y responde `checklistCount` (y `checklistFailed` si algún ítem falló).
|
|
153
|
+
- `search` documenta `statusId`/`tags` en los resultados (requiere la API con ORQ-536).
|
|
154
|
+
|
|
144
155
|
## Tools
|
|
145
156
|
|
|
146
157
|
Lectura de contexto:
|
|
@@ -239,7 +250,7 @@ create/update sigue siendo criterio del agente y del humano.
|
|
|
239
250
|
|
|
240
251
|
## Estado
|
|
241
252
|
|
|
242
|
-
|
|
253
|
+
161 tools (ver arriba): lectura (contexto, dashboard, workspace, infra, paridad, standup,
|
|
243
254
|
sync report, setup guide, playbooks de etapa y de plataforma, catálogo de
|
|
244
255
|
skills, search) + `list_*` de cada colección + creación/edición de tareas
|
|
245
256
|
(con checklist, verificaciones por plataforma, 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.70.0",
|
|
4
|
+
"description": "Servidor MCP de Orquestra -- 161 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
|
@@ -80,6 +80,10 @@ export async function apiPost(config, path, { projectId, ...body }, query = {})
|
|
|
80
80
|
return request(buildUrl(config, path, { projectId, ...query }), { method: 'POST', headers: jsonHeaders(config), body: JSON.stringify(body) })
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
export async function apiPut(config, path, { projectId, ...body }, query = {}) {
|
|
84
|
+
return request(buildUrl(config, path, { projectId, ...query }), { method: 'PUT', headers: jsonHeaders(config), body: JSON.stringify(body) })
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
export async function apiPatch(config, path, { projectId, id, ...body }, query = {}) {
|
|
84
88
|
return request(buildUrl(config, `${path}/${id}`, { projectId, ...query }), { method: 'PATCH', headers: jsonHeaders(config), body: JSON.stringify(body) })
|
|
85
89
|
}
|
|
@@ -101,8 +105,8 @@ export async function apiDelete(config, path, { projectId, id }, query = {}) {
|
|
|
101
105
|
|
|
102
106
|
// DELETE sobre un recurso singleton SIN id en la ruta (ej. /v1/client-portal,
|
|
103
107
|
// ORQ-332) -- mismo criterio que apiPatchPath.
|
|
104
|
-
export async function apiDeletePath(config, path, { projectId }) {
|
|
105
|
-
return request(buildUrl(config, path, { projectId }), { method: 'DELETE', headers: auth(config) })
|
|
108
|
+
export async function apiDeletePath(config, path, { projectId, ...query }) {
|
|
109
|
+
return request(buildUrl(config, path, { projectId, ...query }), { method: 'DELETE', headers: auth(config) })
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
// PUT crudo a una presigned URL de S3 (createAttachmentUploadUrl en
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ORQ-532: lógica pura de close_task. La API no recorre estatus sola (cada estatus solo permite pasar a su
|
|
2
|
+
// `nextIds`), así que cerrar una tarea que está en Backlog/Bloqueado/etc. exige varios PATCH. Aquí se calcula
|
|
3
|
+
// el camino más corto hacia el estatus de cierre normal (final, no cancelado ni archivo), sin tocar la red.
|
|
4
|
+
|
|
5
|
+
const isClosed = s => !!(s && (s.isFinal || s.isCancel || s.isArchive))
|
|
6
|
+
const isReview = s => /revisi[oó]n|review/i.test(s?.name || '')
|
|
7
|
+
|
|
8
|
+
/** Normaliza la respuesta de GET /v1/statuses (arreglo directo o { items }). */
|
|
9
|
+
export function normalizeStatuses(data) {
|
|
10
|
+
const list = Array.isArray(data) ? data : Array.isArray(data?.items) ? data.items : Array.isArray(data?.statuses) ? data.statuses : []
|
|
11
|
+
return list.filter(s => s && s.id)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Estatus de cierre normal: final y ni cancelado ni archivo. */
|
|
15
|
+
export function closeTargets(statuses) {
|
|
16
|
+
return statuses.filter(s => s.isFinal && !s.isCancel && !s.isArchive)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** BFS desde `fromId` por nextIds; no transita por estatus cerrados (final/cancelado/archivo) salvo como destino.
|
|
20
|
+
* Devuelve el camino más corto como arreglo de estatus (sin incluir el origen) hacia `targetId`, o null. */
|
|
21
|
+
function shortestPath(byId, fromId, targetId) {
|
|
22
|
+
const prev = new Map([[fromId, null]])
|
|
23
|
+
const queue = [fromId]
|
|
24
|
+
while (queue.length) {
|
|
25
|
+
const cur = queue.shift()
|
|
26
|
+
if (cur === targetId) break
|
|
27
|
+
for (const nid of byId.get(cur)?.nextIds || []) {
|
|
28
|
+
const next = byId.get(nid)
|
|
29
|
+
if (!next || prev.has(nid)) continue
|
|
30
|
+
if (isClosed(next) && nid !== targetId) continue
|
|
31
|
+
prev.set(nid, cur)
|
|
32
|
+
queue.push(nid)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!prev.has(targetId)) return null
|
|
36
|
+
const out = []
|
|
37
|
+
for (let id = targetId; id !== fromId; id = prev.get(id)) out.unshift(byId.get(id))
|
|
38
|
+
return out
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {object[]} statuses estatuses del proyecto ({ id, name, order, nextIds, isFinal, isCancel, isArchive })
|
|
43
|
+
* @param {string} fromId estatus actual de la tarea
|
|
44
|
+
* @returns {{ alreadyClosed: true, current: object } | { path: object[], target: object } | { error: string }}
|
|
45
|
+
*/
|
|
46
|
+
export function findClosePath(statuses, fromId) {
|
|
47
|
+
const byId = new Map(statuses.map(s => [s.id, s]))
|
|
48
|
+
const current = byId.get(fromId)
|
|
49
|
+
if (!current) return { error: `El estatus actual de la tarea (${fromId}) no existe en el proyecto` }
|
|
50
|
+
if (isClosed(current)) return { alreadyClosed: true, current }
|
|
51
|
+
const targets = closeTargets(statuses)
|
|
52
|
+
if (!targets.length) return { error: 'El proyecto no tiene un estatus final (isFinal) al que cerrar' }
|
|
53
|
+
const candidates = targets
|
|
54
|
+
.map(t => ({ target: t, path: shortestPath(byId, fromId, t.id) }))
|
|
55
|
+
.filter(c => c.path)
|
|
56
|
+
if (!candidates.length) {
|
|
57
|
+
return { error: `No hay camino por nextIds desde "${current.name}" hasta un estatus final (${targets.map(t => `"${t.name}"`).join(', ')}). Revisa el flujo con list_statuses.` }
|
|
58
|
+
}
|
|
59
|
+
// Más corto; a igual largo, el que pasa por "En revisión" (flujo normal de cierre); luego el de menor order.
|
|
60
|
+
candidates.sort((a, b) =>
|
|
61
|
+
a.path.length - b.path.length
|
|
62
|
+
|| Number(b.path.some(isReview)) - Number(a.path.some(isReview))
|
|
63
|
+
|| (a.target.order ?? 0) - (b.target.order ?? 0))
|
|
64
|
+
return { path: candidates[0].path, target: candidates[0].target }
|
|
65
|
+
}
|