orquestra-mcp 1.44.0 → 1.46.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 +2 -2
- package/src/platformPlaybooks.mjs +1 -1
- package/src/server.mjs +37 -3
- package/src/setup.mjs +66 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.0",
|
|
4
4
|
"description": "Servidor MCP de Orquestra -- 135 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": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=18"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://orquestra.me/mcp",
|
|
18
|
+
"homepage": "https://www.orquestra.me/mcp",
|
|
19
19
|
"keywords": [
|
|
20
20
|
"mcp",
|
|
21
21
|
"model-context-protocol",
|
|
@@ -526,7 +526,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
526
526
|
{
|
|
527
527
|
title: 'Medir y corregir Core Web Vitals en PageSpeed Insights (móvil)',
|
|
528
528
|
description: 'Correr PageSpeed Insights sobre la home y las páginas clave. Objetivo: LCP < 2.5s, CLS < 0.1, INP < 200ms. Atacar primero imágenes sin comprimir, fuentes bloqueantes y JS sin dividir.',
|
|
529
|
-
type: 'tech', priority: 'medium', tags: ['seo', 'web', 'performance'], offsetDays: 5,
|
|
529
|
+
type: 'tech', priority: 'medium', tags: ['seo', 'web', 'performance'], offsetDays: 5, checkKey: 'core_web_vitals',
|
|
530
530
|
},
|
|
531
531
|
{
|
|
532
532
|
title: 'Fijar dominio canónico y redirecciones 301 (www / http → https)',
|
package/src/server.mjs
CHANGED
|
@@ -337,6 +337,26 @@ export function createServer() {
|
|
|
337
337
|
{}
|
|
338
338
|
)
|
|
339
339
|
|
|
340
|
+
// PageSpeed Insights por distribución web (ORQ-377). La última medición
|
|
341
|
+
// también viene en list_distributions (campo `pagespeed` de cada una).
|
|
342
|
+
registerRead(
|
|
343
|
+
'get_pagespeed',
|
|
344
|
+
'/v1/pagespeed',
|
|
345
|
+
'Historial de mediciones de PageSpeed Insights (móvil) de una distribución web, más reciente primero: scores de Performance/Accesibilidad/Buenas prácticas/SEO, métricas de laboratorio (LCP, CLS, TBT), Core Web Vitals de usuarios reales (CrUX: LCP, INP, CLS con categoría FAST/AVERAGE/SLOW; null si el sitio no tiene tráfico suficiente), oportunidades con ahorro estimado y auditorías que fallan. Se mide solo cada lunes; para una medición nueva usa measure_pagespeed.',
|
|
346
|
+
{
|
|
347
|
+
distributionId: z.string().describe('Distribución type="web" con domain configurado (ver list_distributions)'),
|
|
348
|
+
limit: z.number().int().min(1).max(100).optional().describe('Cuántas mediciones traer (default 20)'),
|
|
349
|
+
},
|
|
350
|
+
(args, projectId) => ({ projectId, distributionId: args.distributionId, limit: args.limit })
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
registerCreate(
|
|
354
|
+
'measure_pagespeed',
|
|
355
|
+
'/v1/pagespeed',
|
|
356
|
+
'Pide una medición nueva de PageSpeed Insights (móvil) para una distribución web con dominio. Corre en segundo plano (10-40 s): responde de inmediato con status "running"; el resultado aparece después en el campo `pagespeed` de la distribución (list_distributions) y en get_pagespeed. 409 si ya hay una en curso.',
|
|
357
|
+
{ distributionId: z.string().describe('Distribución type="web" con domain configurado') }
|
|
358
|
+
)
|
|
359
|
+
|
|
340
360
|
registerRead(
|
|
341
361
|
'get_infra_status',
|
|
342
362
|
'/v1/infra',
|
|
@@ -2637,12 +2657,12 @@ export function createServer() {
|
|
|
2637
2657
|
|
|
2638
2658
|
registerRead('list_agents', '/v1/agents',
|
|
2639
2659
|
'Lista los agentes (kind+máquina+stack+ruta local) configurados en un proyecto. Úsala antes de create_agent o de create_agent_run con stack, para saber si ya existe uno.',
|
|
2640
|
-
{ kind: z.enum(AGENT_KINDS).optional().describe('Filtra por kind: "qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse) o "screenshots" (captura de pantallas para el checklist de publicación, solo ios/android)') },
|
|
2660
|
+
{ kind: z.enum(AGENT_KINDS).optional().describe('Filtra por kind: "qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse: performance/accesibilidad/buenas prácticas/SEO + LCP/CLS/TBT) o "screenshots" (captura de pantallas para el checklist de publicación, solo ios/android)') },
|
|
2641
2661
|
(args, projectId) => ({ projectId, ...(args.kind ? { kind: args.kind } : {}) }))
|
|
2642
2662
|
|
|
2643
2663
|
registerCreate('create_agent', '/v1/agents', 'Crea un agente: asocia un kind ("qa" | "security" | "performance" | "screenshots") a una máquina (runner, ver Workspace → Máquinas), un stack de este proyecto y una ruta de checkout en ESA máquina. kind="performance" además necesita perfUrl.', {
|
|
2644
2664
|
name: z.string().describe('Nombre a mostrar, ej. "Agente web"'),
|
|
2645
|
-
kind: z.enum(AGENT_KINDS).default('qa').describe('"qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse) o "screenshots" (captura de pantallas para tiendas, solo stack ios/android) -- default "qa"'),
|
|
2665
|
+
kind: z.enum(AGENT_KINDS).default('qa').describe('"qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse: performance/accesibilidad/buenas prácticas/SEO + LCP/CLS/TBT) o "screenshots" (captura de pantallas para tiendas, solo stack ios/android) -- default "qa"'),
|
|
2646
2666
|
runnerId: z.string().describe('ID de la máquina emparejada -- ver list_agents o Workspace → Máquinas'),
|
|
2647
2667
|
stack: z.enum(AGENT_STACKS).optional().describe('Requerido para los 4 kinds. security/performance solo tienen lógica real para "web" hoy; screenshots solo para "ios"/"android"'),
|
|
2648
2668
|
localPath: z.string().optional().describe('Requerido para los 4 kinds -- ruta absoluta del checkout en esa máquina'),
|
|
@@ -2657,7 +2677,14 @@ export function createServer() {
|
|
|
2657
2677
|
plannedRunAt: z.string().optional().describe('YYYY-MM-DD -- recordatorio manual de "próxima corrida planeada". Ningún kind tiene scheduling real (todo trigger es manual/push): esto NO dispara ninguna corrida sola, solo alimenta el calendario global.'),
|
|
2658
2678
|
perfUrl: z.string().optional().describe('Requerido si kind="performance" -- URL viva (ej. staging/prod) contra la que corre Lighthouse'),
|
|
2659
2679
|
maxBundleSizeKb: z.number().optional().describe('Específico de kind="performance" -- umbral en KB para el paso de tamaño de build (default 5000)'),
|
|
2660
|
-
minPerfScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Lighthouse (0-100) para pasar (default 50)'),
|
|
2680
|
+
minPerfScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Lighthouse en Performance (0-100) para pasar (default 50)'),
|
|
2681
|
+
perfUrls: z.array(z.string()).optional().describe('Específico de kind="performance" -- URLs ADICIONALES a perfUrl (ej. /precios, /blog): Lighthouse corre un paso por cada una'),
|
|
2682
|
+
minAccessibilityScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Accesibilidad (0-100, default 90)'),
|
|
2683
|
+
minBestPracticesScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Buenas prácticas (0-100, default 90)'),
|
|
2684
|
+
minSeoScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de SEO (0-100, default 90)'),
|
|
2685
|
+
maxLcpMs: z.number().optional().describe('Específico de kind="performance" -- LCP máximo en ms (default 2500, umbral "bueno" de Google)'),
|
|
2686
|
+
maxCls: z.number().optional().describe('Específico de kind="performance" -- CLS máximo (default 0.1)'),
|
|
2687
|
+
maxTbtMs: z.number().optional().describe('Específico de kind="performance" -- TBT máximo en ms (default 200, proxy de laboratorio de INP)'),
|
|
2661
2688
|
})
|
|
2662
2689
|
|
|
2663
2690
|
registerUpdate('update_agent', '/v1/agents', 'Edita un agente existente.', {
|
|
@@ -2678,6 +2705,13 @@ export function createServer() {
|
|
|
2678
2705
|
perfUrl: z.string().optional().describe('Ver create_agent -- específico de kind="performance"'),
|
|
2679
2706
|
maxBundleSizeKb: z.number().optional(),
|
|
2680
2707
|
minPerfScore: z.number().optional(),
|
|
2708
|
+
perfUrls: z.array(z.string()).optional().describe('Ver create_agent -- reemplaza la lista completa, [] la vacía'),
|
|
2709
|
+
minAccessibilityScore: z.number().optional(),
|
|
2710
|
+
minBestPracticesScore: z.number().optional(),
|
|
2711
|
+
minSeoScore: z.number().optional(),
|
|
2712
|
+
maxLcpMs: z.number().optional(),
|
|
2713
|
+
maxCls: z.number().optional(),
|
|
2714
|
+
maxTbtMs: z.number().optional(),
|
|
2681
2715
|
})
|
|
2682
2716
|
|
|
2683
2717
|
registerCreate('create_agent_run', '/v1/agent-runs', 'Dispara una corrida de agente (qa: lint/tests/build/E2E; security: npm audit + gitleaks; performance: build size + Lighthouse; screenshots: captura de pantallas para el checklist de publicación) en la rama indicada. Necesita al menos un agente activo para el stack (o falla explícito -- ver list_agents/create_agent). La corrida queda "queued" -- nunca asumas que pasó, confirma con get_agent_run.', {
|
package/src/setup.mjs
CHANGED
|
@@ -78,22 +78,72 @@ export async function runSetup() {
|
|
|
78
78
|
const agentLabelAnswer = await prompt('¿Cómo se llama este agente/herramienta? [Claude Code]: ')
|
|
79
79
|
const agentLabel = agentLabelAnswer.trim() || 'Claude Code'
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
// askIfMissing: si no hay workspace la API no crea nada solo -- avisa
|
|
82
|
+
// (needsWorkspace) y aquí se le pide el nombre al usuario.
|
|
83
|
+
const callBootstrap = async (extra = {}) => {
|
|
84
|
+
const res = await fetch(`${apiBase}/v1/bootstrap`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ agentLabel, askIfMissing: true, ...extra }),
|
|
88
|
+
})
|
|
89
|
+
const body = await res.json()
|
|
90
|
+
if (!res.ok) throw new Error(body?.error || `HTTP ${res.status}`)
|
|
91
|
+
return body
|
|
92
|
+
}
|
|
93
|
+
const askName = async (question) => {
|
|
94
|
+
for (;;) {
|
|
95
|
+
const name = (await prompt(question)).trim()
|
|
96
|
+
if (name) return name
|
|
97
|
+
console.log('El nombre no puede estar vacío.')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let body = await callBootstrap()
|
|
102
|
+
if (body.needsWorkspaceChoice) {
|
|
103
|
+
// Miembro de varios workspaces: elegir aquí no cambia el workspace
|
|
104
|
+
// actual de la web, solo a cuál queda conectada esta carpeta.
|
|
105
|
+
const { workspaces, currentWorkspaceId } = body
|
|
106
|
+
console.log('\nPerteneces a varios workspaces. ¿En cuál quieres trabajar desde esta carpeta?')
|
|
107
|
+
workspaces.forEach((w, i) => console.log(` ${i + 1}. ${w.name}${w.id === currentWorkspaceId ? ' (el que tienes abierto en la web)' : ''}`))
|
|
108
|
+
const currentIdx = workspaces.findIndex(w => w.id === currentWorkspaceId) + 1
|
|
109
|
+
let idx
|
|
110
|
+
for (;;) {
|
|
111
|
+
const answer = (await prompt(currentIdx ? `Número [Enter para usar "${workspaces[currentIdx - 1].name}"]: ` : 'Número: ')).trim()
|
|
112
|
+
idx = answer === '' && currentIdx ? currentIdx : parseInt(answer, 10)
|
|
113
|
+
if (idx >= 1 && idx <= workspaces.length) break
|
|
114
|
+
console.log('Selección inválida.')
|
|
115
|
+
}
|
|
116
|
+
body = await callBootstrap({ workspaceId: workspaces[idx - 1].id })
|
|
117
|
+
} else if (body.needsWorkspace) {
|
|
118
|
+
console.log('\nTodavía no tienes un workspace en Orquestra. Primero vamos a crearlo')
|
|
119
|
+
console.log('(agrupa todos tus proyectos: tu empresa, tu equipo o tu nombre si trabajas solo).')
|
|
120
|
+
const workspaceName = await askName('Nombre del workspace: ')
|
|
121
|
+
body = await callBootstrap({ workspaceName })
|
|
122
|
+
console.log(`Workspace "${workspaceName}" creado.`)
|
|
123
|
+
}
|
|
88
124
|
|
|
89
|
-
const { workspaceId, token,
|
|
125
|
+
const { workspaceId, token, projects } = body
|
|
126
|
+
// La API de producción anterior a ORQ-388 no manda workspaceName.
|
|
127
|
+
const wsLabel = body.workspaceName ? ` del workspace "${body.workspaceName}"` : ''
|
|
90
128
|
let defaultProjectId
|
|
91
129
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
const createProject = async (name) => {
|
|
131
|
+
const res2 = await fetch(`${apiBase}/v1/projects?workspaceId=${workspaceId}`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ name }),
|
|
135
|
+
})
|
|
136
|
+
const newProject = await res2.json()
|
|
137
|
+
if (!res2.ok) throw new Error(newProject?.error || `HTTP ${res2.status}`)
|
|
138
|
+
console.log(`Proyecto "${newProject.name}"${wsLabel} creado y configurado como default.`)
|
|
139
|
+
return newProject.id
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (projects.length === 0) {
|
|
143
|
+
console.log(`\nAhora crea el proyecto${wsLabel} que este MCP va a usar (puedes agregar más después).`)
|
|
144
|
+
defaultProjectId = await createProject(await askName('Nombre del proyecto: '))
|
|
95
145
|
} else {
|
|
96
|
-
console.log(
|
|
146
|
+
console.log(`\nElige el proyecto${wsLabel} que este MCP va a usar por default:`)
|
|
97
147
|
console.log(' 0. (crear un proyecto nuevo)')
|
|
98
148
|
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name}`))
|
|
99
149
|
const singleDefaultIdx = projects.length === 1 ? 1 : null
|
|
@@ -105,20 +155,10 @@ export async function runSetup() {
|
|
|
105
155
|
if (Number.isNaN(idx) || idx < 0 || idx > projects.length) throw new Error('Selección inválida')
|
|
106
156
|
|
|
107
157
|
if (idx === 0) {
|
|
108
|
-
|
|
109
|
-
if (!name.trim()) throw new Error('El nombre no puede estar vacío')
|
|
110
|
-
const res2 = await fetch(`${apiBase}/v1/projects?workspaceId=${workspaceId}`, {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
113
|
-
body: JSON.stringify({ name: name.trim() }),
|
|
114
|
-
})
|
|
115
|
-
const newProject = await res2.json()
|
|
116
|
-
if (!res2.ok) throw new Error(newProject?.error || `HTTP ${res2.status}`)
|
|
117
|
-
defaultProjectId = newProject.id
|
|
118
|
-
console.log(`Proyecto "${newProject.name}" creado y configurado como default.`)
|
|
158
|
+
defaultProjectId = await createProject(await askName('Nombre del proyecto nuevo: '))
|
|
119
159
|
} else {
|
|
120
160
|
defaultProjectId = projects[idx - 1].id
|
|
121
|
-
console.log(`Usando tu proyecto "${projects[idx - 1].name}" como default.`)
|
|
161
|
+
console.log(`Usando tu proyecto "${projects[idx - 1].name}"${wsLabel} como default.`)
|
|
122
162
|
}
|
|
123
163
|
}
|
|
124
164
|
|
|
@@ -141,6 +181,6 @@ export async function runSetup() {
|
|
|
141
181
|
console.log(' Claude Desktop -- agrega esto a tu claude_desktop_config.json:')
|
|
142
182
|
console.log(' { "mcpServers": { "orquestra": { "command": "npx", "args": ["orquestra-mcp"] } } }\n')
|
|
143
183
|
console.log(' ¿Usas Cursor, ChatGPT/Codex o Gemini CLI? La config de cada uno está en:')
|
|
144
|
-
console.log(' https://orquestra.me/mcp#instalacion\n')
|
|
145
|
-
console.log('Docs completas y las
|
|
184
|
+
console.log(' https://www.orquestra.me/mcp#instalacion\n')
|
|
185
|
+
console.log('Docs completas y todas las tools disponibles: https://www.orquestra.me/mcp')
|
|
146
186
|
}
|