orquestra-mcp 1.58.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 +25 -4
- package/package.json +2 -2
- package/src/api.mjs +14 -9
- package/src/closePath.mjs +65 -0
- package/src/platformPlaybooks.mjs +3 -3
- package/src/server.mjs +932 -211
- package/src/setup.mjs +16 -2
- package/src/toolGuides.mjs +46 -0
- package/src/updateCheck.mjs +53 -2
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/
|
|
@@ -42,6 +42,14 @@ de su caché.
|
|
|
42
42
|
publicada. Con `@latest` cada arranque la usa; el servidor que ya está corriendo conserva
|
|
43
43
|
la suya hasta que reconectas (en Claude Code: `/mcp` → reconectar).
|
|
44
44
|
|
|
45
|
+
**Forzar la actualización:** reinicia la sesión (o `/mcp` → reconectar) y, si sigue la
|
|
46
|
+
versión vieja, `npx clear-npx-cache`. Si registraste el MCP como `orquestra-mcp` sin
|
|
47
|
+
`@latest`, migra con un comando:
|
|
48
|
+
`claude mcp remove orquestra && claude mcp add orquestra -e ORQUESTRA_AGENT=claude-code -- npx -y orquestra-mcp@latest`.
|
|
49
|
+
`diagnostico_conexion` (cuando no hay conexión) muestra la versión en uso frente a la
|
|
50
|
+
última publicada y detecta configs sin `@latest`; con conexión, `get_project_context`
|
|
51
|
+
trae `orquestraMcpUpdate` si hay versión nueva.
|
|
52
|
+
|
|
45
53
|
Variable opcional `ORQUESTRA_WEB_URL` para apuntar el login a otro lugar
|
|
46
54
|
(ej. `http://localhost:5173` mientras desarrollas `orquestra-web` local en
|
|
47
55
|
vez de contra producción).
|
|
@@ -133,6 +141,17 @@ claude mcp add orquestra \
|
|
|
133
141
|
-- npx -y orquestra-mcp@latest
|
|
134
142
|
```
|
|
135
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
|
+
|
|
136
155
|
## Tools
|
|
137
156
|
|
|
138
157
|
Lectura de contexto:
|
|
@@ -140,7 +159,9 @@ Lectura de contexto:
|
|
|
140
159
|
| Tool | Args | Descripción |
|
|
141
160
|
|---|---|---|
|
|
142
161
|
| `get_project_context` | `projectId?` | Snapshot compacto del proyecto (fase, tareas abiertas/vencidas/bloqueadas, módulos, ideas, changelog reciente) |
|
|
143
|
-
| `list_tasks` | `projectId?`, `statusId?` | Lista de tareas, filtro opcional por estatus |
|
|
162
|
+
| `list_tasks` | `projectId?`, `statusId?`, `summary?`, `fields?`, `limit?`, `cursor?` | Lista de tareas, filtro opcional por estatus; en proyectos grandes usa `summary`/`fields`/`limit` y pagina con `nextCursor` |
|
|
163
|
+
| `get_task` | `code?` o `id?` | Una tarea completa (checklist y verificaciones) por folio o id; 404 si no existe, 409 si el código es ambiguo |
|
|
164
|
+
| `update_tasks` | `items[]` (1-50), `opId?` | Edita varias tareas en una llamada; respuesta parcial por ítem |
|
|
144
165
|
| `search` | `projectId?`, `q` | Búsqueda por texto en tareas/notas/ideas/docs |
|
|
145
166
|
| `get_dashboard_summary` | `projectId?` | Hoy/vencidas/bloqueadas, salud de módulos, actividad reciente |
|
|
146
167
|
| `get_project_findings` | `projectId?` | Qué le falta al proyecto, priorizado por severidad (determinístico, sin créditos de IA) |
|
|
@@ -166,7 +187,7 @@ parcial: la lista completa está en `src/server.mjs`:
|
|
|
166
187
|
| Entidad | Tools | Notas |
|
|
167
188
|
|---|---|---|
|
|
168
189
|
| Tareas | `create_task`, `update_task` | `update_task` incluye `scope` — úsalo para resolver desfases de paridad |
|
|
169
|
-
| Verificaciones por plataforma | `list_verifications`, `add_verification`, `update_verification`, `delete_verification`, `verify_with_agent` | Confirmaciones (humanas o de agente) de que la tarea quedó bien en web/iOS/Android/API — NO van en el checklist. La tarea no cierra con verificaciones abiertas y se cierra sola al aprobarse la última; `failed` (con `note`) la regresa a su estatus anterior. También: `verifications` en `create_task`, `waiveVerifications` en `update_task` (solo con confirmación del usuario) y `verificationId` en `create_agent_run` |
|
|
190
|
+
| Verificaciones por plataforma | `list_verifications`, `add_verification`, `add_verifications` (lote), `update_verification`, `delete_verification`, `verify_with_agent` | Confirmaciones (humanas o de agente) de que la tarea quedó bien en web/iOS/Android/API — NO van en el checklist. La tarea no cierra con verificaciones abiertas y se cierra sola al aprobarse la última; `failed` (con `note`) la regresa a su estatus anterior. También: `verifications` en `create_task`, `waiveVerifications` en `update_task` (solo con confirmación del usuario) y `verificationId` en `create_agent_run` |
|
|
170
191
|
| Ideas | `list_ideas`, `create_idea`, `update_idea` | |
|
|
171
192
|
| Changelog | `list_changelog`, `create_changelog_entry`, `update_changelog_entry` | Estilo Keep a Changelog |
|
|
172
193
|
| Notas | `list_notes`, `create_note`, `update_note` | |
|
|
@@ -229,7 +250,7 @@ create/update sigue siendo criterio del agente y del humano.
|
|
|
229
250
|
|
|
230
251
|
## Estado
|
|
231
252
|
|
|
232
|
-
|
|
253
|
+
161 tools (ver arriba): lectura (contexto, dashboard, workspace, infra, paridad, standup,
|
|
233
254
|
sync report, setup guide, playbooks de etapa y de plataforma, catálogo de
|
|
234
255
|
skills, search) + `list_*` de cada colección + creación/edición de tareas
|
|
235
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
|
@@ -68,19 +68,24 @@ function buildUrl(config, path, params) {
|
|
|
68
68
|
// Todas pasan por request() (http.mjs, ORQ-343): plazo por intento, cancelación de la tool y
|
|
69
69
|
// reintentos acotados -- las lecturas ante fallas transitorias, las escrituras solo ante el
|
|
70
70
|
// throttling del API Gateway (no llegó a la Lambda, no puede duplicar nada).
|
|
71
|
-
|
|
71
|
+
// x-orquestra-client: la API lo lee para el origen de la bitácora de acceso (ORQ-480).
|
|
72
|
+
const auth = config => ({ Authorization: `Bearer ${config.apiToken}`, 'x-orquestra-client': 'mcp' })
|
|
72
73
|
const jsonHeaders = config => ({ ...auth(config), 'Content-Type': 'application/json' })
|
|
73
74
|
|
|
74
75
|
export async function apiGet(config, path, params = {}) {
|
|
75
76
|
return request(buildUrl(config, path, params), { headers: auth(config) })
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
export async function apiPost(config, path, { projectId, ...body }) {
|
|
79
|
-
return request(buildUrl(config, path, { projectId }), { method: 'POST', headers: jsonHeaders(config), body: JSON.stringify(body) })
|
|
79
|
+
export async function apiPost(config, path, { projectId, ...body }, query = {}) {
|
|
80
|
+
return request(buildUrl(config, path, { projectId, ...query }), { method: 'POST', headers: jsonHeaders(config), body: JSON.stringify(body) })
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
export async function
|
|
83
|
-
return request(buildUrl(config,
|
|
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
|
+
|
|
87
|
+
export async function apiPatch(config, path, { projectId, id, ...body }, query = {}) {
|
|
88
|
+
return request(buildUrl(config, `${path}/${id}`, { projectId, ...query }), { method: 'PATCH', headers: jsonHeaders(config), body: JSON.stringify(body) })
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
// Mismo método que apiPatch, pero para un doc singleton SIN id en la ruta
|
|
@@ -94,14 +99,14 @@ export async function apiPatchPath(config, path, { projectId, ...body }) {
|
|
|
94
99
|
// -- ver CLAUDE.md, "único delete_* real de la API/MCP"). Mismo shape que
|
|
95
100
|
// apiPatch (arma la URL como `${path}/${id}`), sin body -- ningún delete de
|
|
96
101
|
// esta API necesita mandar datos, solo identificar el recurso.
|
|
97
|
-
export async function apiDelete(config, path, { projectId, id }) {
|
|
98
|
-
return request(buildUrl(config, `${path}/${id}`, { projectId }), { method: 'DELETE', headers: auth(config) })
|
|
102
|
+
export async function apiDelete(config, path, { projectId, id }, query = {}) {
|
|
103
|
+
return request(buildUrl(config, `${path}/${id}`, { projectId, ...query }), { method: 'DELETE', headers: auth(config) })
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
// DELETE sobre un recurso singleton SIN id en la ruta (ej. /v1/client-portal,
|
|
102
107
|
// ORQ-332) -- mismo criterio que apiPatchPath.
|
|
103
|
-
export async function apiDeletePath(config, path, { projectId }) {
|
|
104
|
-
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) })
|
|
105
110
|
}
|
|
106
111
|
|
|
107
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
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// lo lee directo desde aca (mismo criterio que stages.mjs/foundations.mjs).
|
|
4
4
|
// Puerto de TRACKING_TOOLS de la web: herramientas de medición conectables a una
|
|
5
5
|
// distribución web. El ID vive en distributions.publishForm[key]. `pattern` valida
|
|
6
|
-
// el formato (se serializa como string en la web,
|
|
6
|
+
// el formato (se serializa como string en la web, aquí es el RegExp directo).
|
|
7
7
|
export const TRACKING_TOOLS = [
|
|
8
8
|
{ key: 'ga4_measurement_id', label: 'Google Analytics 4', idLabel: 'ID de medición', pattern: /^G-[A-Z0-9]{6,12}$/, example: 'G-ABC123DEF4', where: 'Google Analytics → Admin → Flujos de datos' },
|
|
9
9
|
{ key: 'meta_pixel_id', label: 'Meta Pixel', idLabel: 'ID del Pixel', pattern: /^\d{8,20}$/, example: '1234567890123456', where: 'Meta Business → Events Manager' },
|
|
@@ -312,7 +312,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
312
312
|
{
|
|
313
313
|
key: 'privacy_policy',
|
|
314
314
|
title: 'Privacy policy URL',
|
|
315
|
-
body: 'Obligatoria para publicar -- Play Console valida que la URL responda 200 al guardarla, no solo que tenga formato de URL. Ojo con apps de una sola página (React Router y similares): si el hosting no tiene configurado el rewrite de SPA (servir index.html para cualquier ruta), una URL como /privacidad puede dar 404 real aunque el deploy haya salido bien -- probar la URL en el navegador ANTES de pegarla
|
|
315
|
+
body: 'Obligatoria para publicar -- Play Console valida que la URL responda 200 al guardarla, no solo que tenga formato de URL. Ojo con apps de una sola página (React Router y similares): si el hosting no tiene configurado el rewrite de SPA (servir index.html para cualquier ruta), una URL como /privacidad puede dar 404 real aunque el deploy haya salido bien -- probar la URL en el navegador ANTES de pegarla aquí, no confiar en que "ya se desplegó" alcanza.',
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
key: 'ads_declaration',
|
|
@@ -332,7 +332,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
332
332
|
{
|
|
333
333
|
key: 'content_rating',
|
|
334
334
|
title: 'Content rating questionnaire (IARC)',
|
|
335
|
-
body: 'Cuestionario largo, con sub-preguntas que se despliegan según tus respuestas. Puntos donde el criterio NO es obvio: "User Content Sharing" (¿la app permite nativamente intercambiar texto/voz/imágenes/audio entre usuarios?) es MÁS AMPLIO que el "User-Generated Content" de Apple -- Apple exime contenido privado dentro de un equipo, Google NO tiene esa excepción, así que comentarios/notas compartidas entre miembros de un workspace SÍ cuentan como "Yes"
|
|
335
|
+
body: 'Cuestionario largo, con sub-preguntas que se despliegan según tus respuestas. Puntos donde el criterio NO es obvio: "User Content Sharing" (¿la app permite nativamente intercambiar texto/voz/imágenes/audio entre usuarios?) es MÁS AMPLIO que el "User-Generated Content" de Apple -- Apple exime contenido privado dentro de un equipo, Google NO tiene esa excepción, así que comentarios/notas compartidas entre miembros de un workspace SÍ cuentan como "Yes" aquí aunque sean privados. Si contestas "Yes" ahí, "¿las interacciones pueden limitarse a usuarios invitados?" responde "Yes" si el acceso es por invitación (esto ayuda a mantener la clasificación baja). El resto (violencia, sexual, lenguaje, drogas, apuestas) va en "No"/"None" para una app B2B típica -- resultado esperado: la clasificación más baja (3+/PEGI 3/Everyone).',
|
|
336
336
|
},
|
|
337
337
|
{
|
|
338
338
|
key: 'target_audience',
|