orquestra-mcp 1.17.0 → 1.18.1
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 +14 -8
- package/src/setup.mjs +7 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.1",
|
|
4
4
|
"description": "Servidor MCP de Orquestra -- 89 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
|
@@ -508,6 +508,7 @@ export function createServer() {
|
|
|
508
508
|
categoryId: z.string().optional(),
|
|
509
509
|
moduleId: z.string().describe('OBLIGATORIO -- módulo al que pertenece esta tarea, para poder agruparla/revisarla después. Si no existe uno relacionado, llamá primero a list_modules (para no duplicar) y create_module.'),
|
|
510
510
|
dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').describe('OBLIGATORIO -- fecha estimada de término (YYYY-MM-DD), calculada con criterio real según prioridad/tamaño del trabajo (mismo criterio que la "revisión de sincronización" de get_sync_report), nunca un valor arbitrario.'),
|
|
511
|
+
reviewDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').optional().describe('Fecha para volver a MIRAR esta tarea -- recordatorio, NO un entregable. Úsalo para tareas de espera sin deadline real (ej. "esperar respuesta de Apple y revisar en 5 días"): en ese caso NO llenes dueDate, llena reviewDate. Distinto de dueDate (fecha de término real de un entregable) -- una tarea puede tener ambos, uno solo, o ninguno. Dispara un aviso (notificación + correo) al assignedTo el día que llega, un solo disparo.'),
|
|
511
512
|
assignedTo: z.string().optional().describe('uid del miembro asignado'),
|
|
512
513
|
tags: z.array(z.string()).optional(),
|
|
513
514
|
scope: z.array(z.object({ distId: z.string(), layer: z.string().optional(), done: z.boolean() })).optional()
|
|
@@ -543,6 +544,7 @@ export function createServer() {
|
|
|
543
544
|
categoryId: z.string().optional(),
|
|
544
545
|
moduleId: z.string().optional(),
|
|
545
546
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
547
|
+
reviewDate: z.string().optional().describe('YYYY-MM-DD. Ver create_task -- recordatorio para volver a revisar, distinto de dueDate. Mandá "" para quitarlo.'),
|
|
546
548
|
assignedTo: z.string().optional(),
|
|
547
549
|
tags: z.array(z.string()).optional(),
|
|
548
550
|
scope: z.array(z.object({ distId: z.string(), layer: z.string().optional(), done: z.boolean() })).optional()
|
|
@@ -620,16 +622,17 @@ export function createServer() {
|
|
|
620
622
|
server.registerTool(
|
|
621
623
|
'connect_postproxy_channel',
|
|
622
624
|
{
|
|
623
|
-
description: 'Inicia la conexión de un canal (facebook/instagram/tiktok/linkedin) a Postproxy. Devuelve UNA de dos formas: {url} -- OAuth hosteado por Postproxy, el usuario tiene que abrirlo y completarlo a mano (el agente no puede terminar un OAuth por su cuenta), pásale esa URL tal cual; o {needsPageSelection, profileId, pages} -- pasa con facebook y linkedin, cuando ya hay un login de esa red conectado en la cuenta (un solo login da acceso a varias Pages de Facebook, o al perfil personal y las Organization Pages de LinkedIn, no hace falta repetir el OAuth) y hace falta elegir cuál corresponde a este canal. Esa elección hoy es un flujo de la web (Cuentas conectadas) -- si te llega `needsPageSelection`, dile al usuario que complete la selección ahí, esta tool no tiene una contraparte para elegir la página. Solo para los tipos que soporta Postproxy -- un canal newsletter (Buttondown) se conecta desde la web, no con esta tool.',
|
|
625
|
+
description: 'Inicia la conexión de un canal (facebook/instagram/tiktok/linkedin) a Postproxy. Devuelve UNA de dos formas: {url} -- OAuth hosteado por Postproxy, el usuario tiene que abrirlo y completarlo a mano (el agente no puede terminar un OAuth por su cuenta), pásale esa URL tal cual; o {needsPageSelection, profileId, profileLabel, pages} -- pasa con facebook y linkedin, cuando ya hay un login de esa red conectado en la cuenta (un solo login da acceso a varias Pages de Facebook, o al perfil personal y las Organization Pages de LinkedIn, no hace falta repetir el OAuth) y hace falta elegir cuál corresponde a este canal. Esa elección hoy es un flujo de la web (Cuentas conectadas) -- si te llega `needsPageSelection`, dile al usuario que complete la selección ahí, esta tool no tiene una contraparte para elegir la página. Modelo real de Postproxy: cada perfil (login) vive en UN solo profile group y Orquestra usa un group por proyecto -- el OAuth de esta tool deja el perfil nuevo en el group de este proyecto; una cuenta de Instagram/TikTok conectada desde otro proyecto NO se puede reusar acá (sería publicar en la cuenta de otro proyecto), hay que conectar la cuenta correcta. Solo para los tipos que soporta Postproxy -- un canal newsletter (Buttondown) se conecta desde la web, no con esta tool.',
|
|
624
626
|
inputSchema: {
|
|
625
627
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
626
628
|
channelId: z.string().describe('Canal a conectar (ver list_channels) -- su `type` tiene que ser facebook/instagram/tiktok/linkedin'),
|
|
627
629
|
platform: z.string().describe('Plataforma tal cual la espera Postproxy (mismo valor que channel.type -- ej. "instagram")'),
|
|
630
|
+
forceOAuth: z.boolean().optional().describe('true = iniciar un OAuth nuevo aunque ya exista un login de esa red en la cuenta de Postproxy (conectar OTRA cuenta de Facebook/LinkedIn, o reconectar una vencida). Sin esto, para facebook/linkedin se reusa el login existente y se devuelve needsPageSelection.'),
|
|
628
631
|
},
|
|
629
632
|
},
|
|
630
|
-
async ({ projectId, channelId, platform }) => {
|
|
633
|
+
async ({ projectId, channelId, platform, forceOAuth }) => {
|
|
631
634
|
try {
|
|
632
|
-
const data = await apiPost(config, `/v1/channels/${channelId}/connect-postproxy`, { projectId: resolveProjectId(projectId), platform })
|
|
635
|
+
const data = await apiPost(config, `/v1/channels/${channelId}/connect-postproxy`, { projectId: resolveProjectId(projectId), platform, ...(forceOAuth ? { forceOAuth: true } : {}) })
|
|
633
636
|
return textResult(data)
|
|
634
637
|
} catch (err) {
|
|
635
638
|
return errorResult(err)
|
|
@@ -640,15 +643,16 @@ export function createServer() {
|
|
|
640
643
|
server.registerTool(
|
|
641
644
|
'publish_content_postproxy',
|
|
642
645
|
{
|
|
643
|
-
description: 'Publica o programa una pieza de Contenido en redes sociales vía Postproxy -- solo funciona si la pieza tiene al menos una variante en estatus "approved" y su canal ya está conectado (ver connect_postproxy_channel/list_channels). Respeta approvalRequired -- no la llames para saltarte una aprobación pendiente.',
|
|
646
|
+
description: 'Publica o programa una pieza de Contenido en redes sociales vía Postproxy -- solo funciona si la pieza tiene al menos una variante en estatus "approved" y su canal ya está conectado (ver connect_postproxy_channel/list_channels). Respeta approvalRequired -- no la llames para saltarte una aprobación pendiente. Cada variante aprobada va en SU PROPIO post de Postproxy (caption/media/fecha propios); antes de cada uno el server verifica el perfil EN VIVO y rechaza con un 400 claro si la cuenta está vencida, ya no existe, o (Instagram/TikTok) vive en el group de Postproxy de otro proyecto -- es decir, el canal apunta a la cuenta de otro proyecto y hay que reconectarlo. Devuelve {posts:[{channelId, postproxyPostId, status}], failed:[{channelId, channelName, error}]}: con éxito parcial responde 200 igual, así que revisa `failed` y cuéntaselo al usuario -- cada variante queda con su externalGuid o su publishError en la pieza.',
|
|
644
647
|
inputSchema: {
|
|
645
648
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
646
649
|
contentId: z.string().describe('Pieza de Contenido a publicar (ver list_content)'),
|
|
650
|
+
channelId: z.string().optional().describe('Publica solo la variante de ese canal. Sin channelId se publican todas las variantes aprobadas de la pieza, cada una en su propio post.'),
|
|
647
651
|
},
|
|
648
652
|
},
|
|
649
|
-
async ({ projectId, contentId }) => {
|
|
653
|
+
async ({ projectId, contentId, channelId }) => {
|
|
650
654
|
try {
|
|
651
|
-
const data = await apiPost(config, `/v1/content/${contentId}/publish-postproxy`, { projectId: resolveProjectId(projectId) })
|
|
655
|
+
const data = await apiPost(config, `/v1/content/${contentId}/publish-postproxy`, { projectId: resolveProjectId(projectId), ...(channelId ? { channelId } : {}) })
|
|
652
656
|
return textResult(data)
|
|
653
657
|
} catch (err) {
|
|
654
658
|
return errorResult(err)
|
|
@@ -1136,7 +1140,7 @@ export function createServer() {
|
|
|
1136
1140
|
)
|
|
1137
1141
|
|
|
1138
1142
|
registerRead('list_changelog', '/v1/changelog',
|
|
1139
|
-
'Lista TODAS las entradas de changelog de un proyecto, con todos sus campos (id, section, text, version, distributions, repo, taskId, createdAt, createdBy) -- a diferencia de get_dashboard_summary/get_project_context, que solo traen un resumen recortado (texto/título, sin repo ni distributions). Útil para auditar (ej. "¿qué entradas viejas no tienen repo?") antes de update_changelog_entry.',
|
|
1143
|
+
'Lista TODAS las entradas de changelog de un proyecto, con todos sus campos (id, section, text, version, distributions, repo, taskId, moduleId, createdAt, createdBy) -- a diferencia de get_dashboard_summary/get_project_context, que solo traen un resumen recortado (texto/título, sin repo ni distributions). Útil para auditar (ej. "¿qué entradas viejas no tienen repo?") antes de update_changelog_entry.',
|
|
1140
1144
|
{}, (args, projectId) => ({ projectId }))
|
|
1141
1145
|
|
|
1142
1146
|
registerCreate(
|
|
@@ -1153,12 +1157,13 @@ export function createServer() {
|
|
|
1153
1157
|
})).optional().describe('Distribuciones a las que aplica esta entrada (ver list_distributions) -- vacío/omitido = aplica a todo el proyecto. Cada una puede tener su propia versión dentro de esta entrada.'),
|
|
1154
1158
|
repo: z.string().optional().describe('"owner/name" del repo de origen del commit/PR que generó esta entrada, si se sabe -- alimenta el chip de repo en la tarjeta de versión.'),
|
|
1155
1159
|
taskId: z.string().optional().describe('Tarea que esta entrada documenta o cierra, si se sabe (ver list_tasks) -- alimenta el chip de tarea, clicable al detalle, en la tarjeta de versión.'),
|
|
1160
|
+
moduleId: z.string().optional().describe('Módulo al que pertenece esta entrada, si se sabe (ver list_modules). Agregado ORQ-178 -- también se puede llenar después vía accept_suggestion sobre una sugerencia kind="module_match" que generó la reconciliación de orquestra-infra.'),
|
|
1156
1161
|
plannedReleaseDate: z.string().optional().describe('YYYY-MM-DD -- fecha objetivo de corte de esta versión (solo tiene sentido si la entrada queda en Unreleased). Puramente informativo, alimenta el calendario global -- no dispara release_changelog_version solo.'),
|
|
1157
1162
|
}
|
|
1158
1163
|
)
|
|
1159
1164
|
|
|
1160
1165
|
registerUpdate('update_changelog_entry', '/v1/changelog',
|
|
1161
|
-
'Edita una entrada de changelog existente (section/text/version/distributions/repo/taskId) -- primera edición puntual por id que existe (antes solo había create + el bulk-update de release_changelog_version). Pensada sobre todo para backfill/corrección (ej. completar "repo" en entradas viejas que se crearon antes de que ese campo se mandara), no para reescribir historia -- si el usuario solo quiere corregir el texto de una entrada reciente por error de tipeo, esto también sirve.',
|
|
1166
|
+
'Edita una entrada de changelog existente (section/text/version/distributions/repo/taskId/moduleId) -- primera edición puntual por id que existe (antes solo había create + el bulk-update de release_changelog_version). Pensada sobre todo para backfill/corrección (ej. completar "repo" en entradas viejas que se crearon antes de que ese campo se mandara), no para reescribir historia -- si el usuario solo quiere corregir el texto de una entrada reciente por error de tipeo, esto también sirve.',
|
|
1162
1167
|
{
|
|
1163
1168
|
section: z.enum(CHANGELOG_SECTIONS).optional(),
|
|
1164
1169
|
text: z.string().optional(),
|
|
@@ -1169,6 +1174,7 @@ export function createServer() {
|
|
|
1169
1174
|
})).optional(),
|
|
1170
1175
|
repo: z.string().optional().describe('"owner/name" del repo de origen.'),
|
|
1171
1176
|
taskId: z.string().optional(),
|
|
1177
|
+
moduleId: z.string().optional().describe('Ver create_changelog_entry.'),
|
|
1172
1178
|
plannedReleaseDate: z.string().optional().describe('YYYY-MM-DD -- ver create_changelog_entry.'),
|
|
1173
1179
|
}
|
|
1174
1180
|
)
|
package/src/setup.mjs
CHANGED
|
@@ -92,15 +92,16 @@ export async function runSetup() {
|
|
|
92
92
|
if (created) {
|
|
93
93
|
defaultProjectId = body.projectId
|
|
94
94
|
console.log(`Se creó un workspace y un proyecto default para ti ("${projects[0].name}") -- puedes renombrarlos cuando quieras desde orquestra-web.`)
|
|
95
|
-
} else if (projects.length === 1) {
|
|
96
|
-
defaultProjectId = projects[0].id
|
|
97
|
-
console.log(`Usando tu proyecto "${projects[0].name}" como default.`)
|
|
98
95
|
} else {
|
|
99
96
|
console.log('\nElige el proyecto que este MCP va a usar por default:')
|
|
100
97
|
console.log(' 0. (crear un proyecto nuevo)')
|
|
101
98
|
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name}`))
|
|
102
|
-
const
|
|
103
|
-
const
|
|
99
|
+
const singleDefaultIdx = projects.length === 1 ? 1 : null
|
|
100
|
+
const answer = await prompt(
|
|
101
|
+
singleDefaultIdx ? `Número [Enter para usar "${projects[0].name}"]: ` : 'Número: '
|
|
102
|
+
)
|
|
103
|
+
const trimmed = answer.trim()
|
|
104
|
+
const idx = trimmed === '' && singleDefaultIdx !== null ? singleDefaultIdx : parseInt(trimmed, 10)
|
|
104
105
|
if (Number.isNaN(idx) || idx < 0 || idx > projects.length) throw new Error('Selección inválida')
|
|
105
106
|
|
|
106
107
|
if (idx === 0) {
|
|
@@ -117,6 +118,7 @@ export async function runSetup() {
|
|
|
117
118
|
console.log(`Proyecto "${newProject.name}" creado y configurado como default.`)
|
|
118
119
|
} else {
|
|
119
120
|
defaultProjectId = projects[idx - 1].id
|
|
121
|
+
console.log(`Usando tu proyecto "${projects[idx - 1].name}" como default.`)
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
|