orquestra-mcp 1.70.0 → 1.72.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 +40 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.72.0",
|
|
4
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": {
|
package/src/server.mjs
CHANGED
|
@@ -2961,15 +2961,16 @@ export function createServer() {
|
|
|
2961
2961
|
server.registerTool(
|
|
2962
2962
|
'upsert_design_tokens',
|
|
2963
2963
|
{
|
|
2964
|
-
description: `Carga en lote (hasta 100) tokens de identidad de diseño: es el camino para poblar o sincronizar la identidad visual de una vez. ${DESIGN_TOKEN_PURPOSE} Cada token se identifica por (name, kind, platform): si existe se actualiza, si no se crea; nada se borra. Se valida todo antes de escribir (un token inválido o repetido en el lote rechaza el lote entero). Devuelve { created, updated, items }.`,
|
|
2964
|
+
description: `Carga en lote (hasta 100) tokens de identidad de diseño: es el camino para poblar o sincronizar la identidad visual de una vez. ${DESIGN_TOKEN_PURPOSE} Cada token se identifica por (name, kind, platform): si existe se actualiza, si no se crea; nada se borra. Se valida todo antes de escribir (un token inválido o repetido en el lote rechaza el lote entero). Devuelve { created, updated, items }; por defecto compact=true y items trae solo {id,name,key?,platform?,action}, con compact=false devuelve las filas completas.`,
|
|
2965
2965
|
inputSchema: {
|
|
2966
2966
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
2967
|
+
compact: z.boolean().optional().describe('true (default) = el lote devuelve items resumidos {id,name,key?,platform?,action}; false = filas completas'),
|
|
2967
2968
|
tokens: z.array(z.object(designTokenFields(false))).min(1).max(100).describe('Tokens a crear/actualizar (1-100)'),
|
|
2968
2969
|
},
|
|
2969
2970
|
},
|
|
2970
|
-
async ({ projectId, tokens }) => {
|
|
2971
|
+
async ({ projectId, tokens, compact }) => {
|
|
2971
2972
|
try {
|
|
2972
|
-
return textResult(await apiPost(config, '/v1/design-tokens/bulk', { projectId: resolveProjectId(projectId), tokens }))
|
|
2973
|
+
return textResult(await apiPost(config, '/v1/design-tokens/bulk', { projectId: resolveProjectId(projectId), tokens, compact: compact ?? true }))
|
|
2973
2974
|
} catch (err) {
|
|
2974
2975
|
return errorResult(err)
|
|
2975
2976
|
}
|
|
@@ -2985,6 +2986,24 @@ export function createServer() {
|
|
|
2985
2986
|
name: z.string().describe('Nombre del bloque, ej: "Encabezado", "Lista de tareas"'),
|
|
2986
2987
|
description: z.string().optional().describe('Qué contiene o hace'),
|
|
2987
2988
|
})).describe('Bloques de la pantalla de arriba a abajo, ej: [{"name":"Encabezado","description":"Título y filtros"},{"name":"Lista de tareas"}]')
|
|
2989
|
+
const navItemKey = z.string().min(1).max(60).regex(/^[a-z0-9.-]+$/, 'key en minúsculas, dígitos, punto o guion').describe('minúsculas, dígitos, punto o guion (≤60), única en el nodo')
|
|
2990
|
+
const uniqueKeys = (arr, ctx) => {
|
|
2991
|
+
const seen = new Set()
|
|
2992
|
+
arr.forEach((it, i) => {
|
|
2993
|
+
if (seen.has(it.key)) ctx.addIssue({ code: 'custom', path: [i, 'key'], message: `key repetida en el nodo: ${it.key}` })
|
|
2994
|
+
seen.add(it.key)
|
|
2995
|
+
})
|
|
2996
|
+
}
|
|
2997
|
+
const navActionsSchema = z.array(z.object({
|
|
2998
|
+
key: navItemKey.describe('Key de la acción, convención entidad.verbo, ej: "task.create", "task.assign"; minúsculas, dígitos, punto o guion (≤60), única en el nodo'),
|
|
2999
|
+
name: z.string().min(1).max(120).describe('Nombre visible de la acción, ej: "Crear tarea" (≤120)'),
|
|
3000
|
+
description: z.string().optional().describe('Qué hace'),
|
|
3001
|
+
roles: z.array(z.string()).optional().describe('Roles que pueden ejecutarla, ej: ["owner","admin"]'),
|
|
3002
|
+
})).max(60).superRefine(uniqueKeys).describe('Acciones que el usuario puede hacer en el nodo (hasta 60). Convención entidad.verbo (task.create); la misma acción lleva la MISMA key en web, iOS y Android: eso permite compararlas en get_design_parity.')
|
|
3003
|
+
const navStatesSchema = z.array(z.object({
|
|
3004
|
+
key: navItemKey.describe('Key del estado; recomendados: loading, empty, error, readonly, no-access, offline, refreshing, search-empty'),
|
|
3005
|
+
note: z.string().optional().describe('Cómo se ve o se comporta en ese estado'),
|
|
3006
|
+
})).max(20).superRefine(uniqueKeys).describe('Estados de la pantalla (hasta 20). Recomendados: loading, empty, error, readonly, no-access, offline, refreshing, search-empty. Misma key en las tres plataformas.')
|
|
2988
3007
|
const navNodeFields = optional => {
|
|
2989
3008
|
const f = {
|
|
2990
3009
|
name: z.string().describe('Nombre visible del nodo, ej: "Tareas"'),
|
|
@@ -2998,6 +3017,8 @@ export function createServer() {
|
|
|
2998
3017
|
order: z.number().optional().describe('Orden entre hermanos'),
|
|
2999
3018
|
description: z.string().optional().describe('Qué hace o muestra'),
|
|
3000
3019
|
blocks: navBlocksSchema.optional(),
|
|
3020
|
+
actions: navActionsSchema.optional(),
|
|
3021
|
+
states: navStatesSchema.optional(),
|
|
3001
3022
|
sourceRef: z.string().optional().describe('Dónde vive en el código, ej: archivo y símbolo'),
|
|
3002
3023
|
}
|
|
3003
3024
|
if (optional) { f.name = f.name.optional(); f.platform = f.platform.optional(); f.kind = f.kind.optional() }
|
|
@@ -3027,10 +3048,10 @@ export function createServer() {
|
|
|
3027
3048
|
}
|
|
3028
3049
|
)
|
|
3029
3050
|
registerCreate('create_design_nav_node', '/v1/design-nav',
|
|
3030
|
-
`Crea un nodo de navegación de diseño. ${NAV_PURPOSE} Para cargar muchos de una vez usa upsert_design_nav, no esta tool en bucle.`,
|
|
3051
|
+
`Crea un nodo de navegación de diseño. ${NAV_PURPOSE} Para cargar muchos de una vez usa upsert_design_nav, no esta tool en bucle. Cada nodo acepta "actions" (convención entidad.verbo, ej. task.create; la misma acción lleva la MISMA key en web, iOS y Android) y "states" (recomendados: loading, empty, error, readonly, no-access, offline, refreshing, search-empty). La matriz de paridad compara primero por capacidades (acciones y estados) y cae a bloques si la referencia no las define.`,
|
|
3031
3052
|
navNodeFields(false))
|
|
3032
3053
|
registerUpdate('update_design_nav_node', '/v1/design-nav',
|
|
3033
|
-
`Edita un nodo de navegación de diseño existente (parche parcial: manda solo lo que cambia; parentId puede mover el nodo o ponerlo en null para dejarlo en la raíz). ${NAV_PURPOSE}`,
|
|
3054
|
+
`Edita un nodo de navegación de diseño existente (parche parcial: manda solo lo que cambia; parentId puede mover el nodo o ponerlo en null para dejarlo en la raíz). Ojo: "actions" y "states" REEMPLAZAN el array completo del nodo (manda la lista final; para mezclar por key usa upsert_design_nav). Convención de actions: entidad.verbo (task.create), misma key en web, iOS y Android; states recomendados: loading, empty, error, readonly, no-access, offline, refreshing, search-empty. La matriz de paridad compara primero por capacidades (acciones y estados) y cae a bloques si la referencia no las define. ${NAV_PURPOSE}`,
|
|
3034
3055
|
navNodeFields(true))
|
|
3035
3056
|
|
|
3036
3057
|
server.registerTool(
|
|
@@ -3075,18 +3096,19 @@ export function createServer() {
|
|
|
3075
3096
|
server.registerTool(
|
|
3076
3097
|
'upsert_design_nav',
|
|
3077
3098
|
{
|
|
3078
|
-
description: `Carga en lote (hasta 300 nodos) la navegación de diseño: es el camino para cargar el árbol completo de una plataforma leyendo su código (rutas del router, tab bar, sidebar, pantallas y sus bloques). ${NAV_PURPOSE} En vez de parentId cada nodo lleva parentKey (la key del padre, que puede venir en el mismo lote; sin parentKey = raíz). Un nodo existente se identifica por (platform, key) o, si no hay key, por (platform, parentId, name): se actualiza, si no se crea; nada se borra. Ejemplo: [{"platform":"web","kind":"section","name":"Ajustes","key":"settings","route":"/settings"},{"platform":"web","kind":"screen","name":"General","key":"settings.general","parentKey":"settings","route":"/settings/general","blocks":[{"name":"Datos del workspace","description":"Nombre y logo"},{"name":"Zona de peligro"}],"
|
|
3099
|
+
description: `Carga en lote (hasta 300 nodos) la navegación de diseño: es el camino para cargar el árbol completo de una plataforma leyendo su código (rutas del router, tab bar, sidebar, pantallas y sus bloques). ${NAV_PURPOSE} En vez de parentId cada nodo lleva parentKey (la key del padre, que puede venir en el mismo lote; sin parentKey = raíz). Un nodo existente se identifica por (platform, key) o, si no hay key, por (platform, parentId, name): se actualiza, si no se crea; nada se borra. "actions" (convención entidad.verbo, ej. task.create; la misma acción = la MISMA key en web, iOS y Android, es lo que permite compararlas) y "states" (recomendados: loading, empty, error, readonly, no-access, offline, refreshing, search-empty) se MEZCLAN por key al actualizar un nodo existente (update_design_nav_node reemplaza el array completo). La paridad compara por capacidades y cae a bloques si la referencia no las define. Ejemplo: [{"platform":"web","kind":"section","name":"Ajustes","key":"settings","route":"/settings"},{"platform":"web","kind":"screen","name":"General","key":"settings.general","parentKey":"settings","route":"/settings/general","blocks":[{"name":"Datos del workspace","description":"Nombre y logo"},{"name":"Zona de peligro"}],"actions":[{"key":"workspace.rename","name":"Renombrar workspace","roles":["owner"]}],"states":[{"key":"loading"},{"key":"readonly"}]}]. Usa la misma key para la misma pantalla en web, iOS y Android. Devuelve { created, updated, items }; por defecto compact=true y items trae solo {id,name,key?,platform?,action}, con compact=false devuelve las filas completas.`,
|
|
3079
3100
|
inputSchema: {
|
|
3080
3101
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
3102
|
+
compact: z.boolean().optional().describe('true (default) = el lote devuelve items resumidos {id,name,key?,platform?,action}; false = filas completas'),
|
|
3081
3103
|
nodes: z.array(z.object({
|
|
3082
3104
|
...navBulkBase,
|
|
3083
3105
|
parentKey: z.string().optional().describe('Key del nodo padre (en este lote o ya existente); ausente = raíz'),
|
|
3084
3106
|
})).min(1).max(300).describe('Nodos a crear/actualizar (1-300)'),
|
|
3085
3107
|
},
|
|
3086
3108
|
},
|
|
3087
|
-
async ({ projectId, nodes }) => {
|
|
3109
|
+
async ({ projectId, nodes, compact }) => {
|
|
3088
3110
|
try {
|
|
3089
|
-
return textResult(await apiPost(config, '/v1/design-nav/bulk', { projectId: resolveProjectId(projectId), nodes }))
|
|
3111
|
+
return textResult(await apiPost(config, '/v1/design-nav/bulk', { projectId: resolveProjectId(projectId), nodes, compact: compact ?? true }))
|
|
3090
3112
|
} catch (err) {
|
|
3091
3113
|
return errorResult(err)
|
|
3092
3114
|
}
|
|
@@ -3158,15 +3180,16 @@ export function createServer() {
|
|
|
3158
3180
|
server.registerTool(
|
|
3159
3181
|
'upsert_design_components',
|
|
3160
3182
|
{
|
|
3161
|
-
description: `Carga en lote (hasta 200) componentes de diseño: es el camino para catalogar los componentes reales leyendo el código de cada plataforma (web, iOS y Android) y llenar platforms[].sourceRef con el archivo y símbolo de cada una. ${COMP_PURPOSE} Cada componente se identifica por name (sin distinguir mayúsculas): si existe se
|
|
3183
|
+
description: `Carga en lote (hasta 200) componentes de diseño: es el camino para catalogar los componentes reales leyendo el código de cada plataforma (web, iOS y Android) y llenar platforms[].sourceRef con el archivo y símbolo de cada una. ${COMP_PURPOSE} Cada componente se identifica por name (sin distinguir mayúsculas): si existe se crea o se MEZCLA, nada se borra. Cargar el MISMO name desde otra plataforma mezcla en vez de reemplazar: platforms se fusiona por plataforma, tokens/usedIn/states se unen y variants se combinan por nombre; para reemplazar de verdad usa update_design_component. Usa el mismo name para el mismo componente en las tres plataformas. Devuelve { created, updated, items, warnings }: warnings ([{name,field,value}], siempre presente) avisa de tokens o usedIn que no existen, corrígelos. Por defecto compact=true y items trae solo {id,name,key?,platform?,action}; con compact=false devuelve las filas completas.`,
|
|
3162
3184
|
inputSchema: {
|
|
3163
3185
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
3186
|
+
compact: z.boolean().optional().describe('true (default) = el lote devuelve items resumidos {id,name,key?,platform?,action}; false = filas completas'),
|
|
3164
3187
|
components: z.array(z.object(designComponentFields(false))).min(1).max(200).describe('Componentes a crear/actualizar (1-200)'),
|
|
3165
3188
|
},
|
|
3166
3189
|
},
|
|
3167
|
-
async ({ projectId, components }) => {
|
|
3190
|
+
async ({ projectId, components, compact }) => {
|
|
3168
3191
|
try {
|
|
3169
|
-
return textResult(await apiPost(config, '/v1/design-components/bulk', { projectId: resolveProjectId(projectId), components }))
|
|
3192
|
+
return textResult(await apiPost(config, '/v1/design-components/bulk', { projectId: resolveProjectId(projectId), components, compact: compact ?? true }))
|
|
3170
3193
|
} catch (err) {
|
|
3171
3194
|
return errorResult(err)
|
|
3172
3195
|
}
|
|
@@ -3178,17 +3201,18 @@ export function createServer() {
|
|
|
3178
3201
|
server.registerTool(
|
|
3179
3202
|
'get_design_parity',
|
|
3180
3203
|
{
|
|
3181
|
-
description: 'Matriz de paridad de diseño del proyecto: una fila por pantalla/componente (key, name, kind, route) y, por plataforma (web, ios, android), una celda con status (same | different | missing | not_applicable), source (auto | manual), nodeId, path, note, taskId y diff. Devuelve además summary (conteos por plataforma y total) y gaps (los huecos: [{key,name,platform,status}]). `ref` es la plataforma de referencia (default web). "distinto" (different) sale de comparar kind y bloques del nodo contra la plataforma de referencia; "falta" (missing) de que no exista un nodo con esa key en esa plataforma. Flujo para cerrar huecos: cada hueco (different | missing) puede convertirse en una tarea hermana con create_task (scope de esa plataforma y parityOf cuando aplique) y luego enlazarse a su celda con set_design_parity_cell(taskId=folio). Si una celda automática es incorrecta (ej. no aplica), fíjala a mano con set_design_parity_cell. Con soloHuecos:true devuelve solo { summary, gaps } para ahorrar tokens.',
|
|
3204
|
+
description: 'Matriz de paridad de diseño del proyecto: una fila por pantalla/componente (key, name, kind, route) y, por plataforma (web, ios, android), una celda con status (same | different | missing | not_applicable), source (auto | manual), nodeId, path, note, taskId y diff. Cuando hay diff, cada celda suma además: basis ("capabilities" si se comparó por acciones y estados, "blocks" si la referencia no los define y se cayó a bloques), coverage, actionsMissing (acciones de la referencia que faltan aquí), actionsOnlyHere, statesMissing y statesOnlyHere; la raíz suma capabilitiesNodes. Devuelve además summary (conteos por plataforma y total) y gaps (los huecos: [{key,name,platform,status}]). `ref` es la plataforma de referencia (default web). "distinto" (different) sale de comparar kind y bloques del nodo contra la plataforma de referencia; "falta" (missing) de que no exista un nodo con esa key en esa plataforma. Flujo para cerrar huecos: cada hueco (different | missing) puede convertirse en una tarea hermana con create_task (scope de esa plataforma y parityOf cuando aplique) y luego enlazarse a su celda con set_design_parity_cell(taskId=folio). Si una celda automática es incorrecta (ej. no aplica), fíjala a mano con set_design_parity_cell. "Igual" (same) significa mismo tipo (kind) y similitud de bloques >= threshold (0..1, default 0.6); cada diff trae su similarity y la respuesta el threshold usado: sube el umbral para ser más estricto, bájalo para tolerar diferencias. Con soloHuecos:true devuelve solo { summary, gaps, threshold } para ahorrar tokens.',
|
|
3182
3205
|
inputSchema: {
|
|
3183
3206
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
3184
3207
|
ref: z.enum(PARITY_PLATFORMS).optional().describe('Plataforma de referencia: web | ios | android (default web)'),
|
|
3185
|
-
soloHuecos: z.boolean().optional().describe('true = devuelve solo summary y
|
|
3208
|
+
soloHuecos: z.boolean().optional().describe('true = devuelve solo summary, gaps y threshold (sin las filas), para ahorrar tokens'),
|
|
3209
|
+
threshold: z.number().min(0).max(1).optional().describe('Umbral de similitud de bloques (0..1, default 0.6) para considerar "same"'),
|
|
3186
3210
|
},
|
|
3187
3211
|
},
|
|
3188
|
-
async ({ projectId, ref, soloHuecos }) => {
|
|
3212
|
+
async ({ projectId, ref, soloHuecos, threshold }) => {
|
|
3189
3213
|
try {
|
|
3190
|
-
const data = await apiGet(config, '/v1/design-parity', { projectId: resolveProjectId(projectId), ref })
|
|
3191
|
-
if (soloHuecos && data && typeof data === 'object') return textResult({ summary: data.summary, gaps: data.gaps })
|
|
3214
|
+
const data = await apiGet(config, '/v1/design-parity', { projectId: resolveProjectId(projectId), ref, threshold })
|
|
3215
|
+
if (soloHuecos && data && typeof data === 'object') return textResult({ summary: data.summary, gaps: data.gaps, threshold: data.threshold })
|
|
3192
3216
|
return textResult(data)
|
|
3193
3217
|
} catch (err) {
|
|
3194
3218
|
return errorResult(err)
|