openprompt-lang 1.2.4 → 1.2.7
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/docs/FRAMEWORK.md +52 -0
- package/docs/OPL-ERRORES.md +504 -0
- package/package.json +1 -1
- package/src/cli/commands-work.js +3 -1
- package/src/commands/init-core.js +19 -7
- package/src/commands/init-existing.js +33 -6
- package/src/commands/init-helpers.js +21 -15
- package/src/commands/integrate.js +5 -5
- package/src/commands/work-context.js +17 -0
- package/src/commands/workflow/close/index.js +2 -1
- package/src/commands/workflow/delivery/index.js +4 -0
- package/src/commands/workflow/discovery/index.js +4 -0
- package/src/commands/workflow/specification/index.js +4 -0
- package/src/core/engine/sandbox.js +7 -3
- package/src/mcp-plan-server.js +12 -5
- package/src/mcp-shared-state.js +25 -0
- package/src/mcp-refactor/mcp-server.js +0 -171
- package/src/mcp-server-backup.js +0 -1913
|
@@ -135,18 +135,23 @@ export async function initExisting(options = {}) {
|
|
|
135
135
|
? JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8")).name || "mi-proyecto"
|
|
136
136
|
: "mi-proyecto"
|
|
137
137
|
|
|
138
|
+
const interactive = options.interactive !== false && !!process.stdin.isTTY
|
|
138
139
|
if (!rebuild) {
|
|
139
140
|
console.log(chalk.cyan(`\n🔧 Inicializando OPL en proyecto existente: ${projectName}\n`))
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
// ── Verificar OpenCode ──
|
|
142
|
-
const interactive = options.interactive !== false && !!process.stdin.isTTY
|
|
143
143
|
await checkAndInstallOpencode(interactive)
|
|
144
144
|
|
|
145
145
|
console.log(chalk.cyan("✨ OPL Init: Entorno detectado."))
|
|
146
146
|
console.log(chalk.yellow("OPL delega ahora la inicialización estratégica a tu Agente IA."))
|
|
147
|
-
console.log(
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
console.log(
|
|
148
|
+
chalk.white(
|
|
149
|
+
"Copia y pega el siguiente prompt en tu chat con la IA (ej. Cline, Cursor, OpenCode, etc.):\n"
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
console.log(
|
|
154
|
+
chalk.bgGray.white(`
|
|
150
155
|
"Asume el rol de Arquitecto Principal OPL. Acabo de ejecutar opl init para un proyecto EXISTENTE llamado '${projectName}'.
|
|
151
156
|
|
|
152
157
|
Tu proceso obligatorio es:
|
|
@@ -155,7 +160,8 @@ Tu proceso obligatorio es:
|
|
|
155
160
|
3. Genera la estructura que falte (docs/, work-context, AGENTS.md, prompt-lang.json, .openprompt/FRAMEWORK.md).
|
|
156
161
|
4. Organiza un backlog inicial de refactorización con la deuda detectada.
|
|
157
162
|
5. Confirma explícitamente que el proyecto quedó estabilizado e indícame el siguiente paso operativo."
|
|
158
|
-
`)
|
|
163
|
+
`)
|
|
164
|
+
)
|
|
159
165
|
return cwd
|
|
160
166
|
}
|
|
161
167
|
|
|
@@ -426,6 +432,27 @@ Tu proceso obligatorio es:
|
|
|
426
432
|
console.log(chalk.yellow(" ⚠️ No se pudo ejecutar upgrade automáticamente."))
|
|
427
433
|
}
|
|
428
434
|
|
|
435
|
+
if (interactive) {
|
|
436
|
+
const { updateKnowledge } = await inquirer.prompt([
|
|
437
|
+
{
|
|
438
|
+
type: "confirm",
|
|
439
|
+
name: "updateKnowledge",
|
|
440
|
+
message:
|
|
441
|
+
"¿Deseas descargar/actualizar la biblioteca de conocimientos (Entorno Académico)?",
|
|
442
|
+
default: false,
|
|
443
|
+
},
|
|
444
|
+
])
|
|
445
|
+
|
|
446
|
+
if (updateKnowledge) {
|
|
447
|
+
try {
|
|
448
|
+
const { initKnowledgeRepo } = await import("./opl-init-knowledge.js")
|
|
449
|
+
await initKnowledgeRepo(options)
|
|
450
|
+
} catch {
|
|
451
|
+
console.log(chalk.yellow(" ⚠️ No se pudo actualizar el conocimiento automáticamente."))
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
429
456
|
console.log(chalk.cyan(`\n✅ Integración OLP reconstruida para: ${projectName}`))
|
|
430
457
|
console.log(chalk.cyan(` Versión OPL: ${config.oplVersion || "desconocida"}`))
|
|
431
458
|
console.log(chalk.gray(" Archivos generados forzaron actualización."))
|
|
@@ -288,7 +288,7 @@ export function setupOpencodeConfig(baseDir, _config) {
|
|
|
288
288
|
const opencodeConfig = {
|
|
289
289
|
$schema: "https://opencode.ai/config.json",
|
|
290
290
|
mcp: {
|
|
291
|
-
openPrompt: { type: "local", command: ["
|
|
291
|
+
openPrompt: { type: "local", command: ["openprompt-lang", "mcp"], enabled: true },
|
|
292
292
|
},
|
|
293
293
|
skills: { paths: [".opencode/skills"] },
|
|
294
294
|
default_agent: "openPrompt",
|
|
@@ -535,35 +535,41 @@ export async function selectFullStack(options) {
|
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
export async function checkAndInstallOpencode(interactive) {
|
|
538
|
-
if (!interactive) return
|
|
538
|
+
if (!interactive) return
|
|
539
539
|
|
|
540
|
-
let isInstalled = false
|
|
540
|
+
let isInstalled = false
|
|
541
541
|
try {
|
|
542
|
-
execSync("opencode --version", { stdio: "ignore" })
|
|
543
|
-
isInstalled = true
|
|
542
|
+
execSync("opencode --version", { stdio: "ignore" })
|
|
543
|
+
isInstalled = true
|
|
544
544
|
} catch (e) {
|
|
545
|
-
isInstalled = false
|
|
545
|
+
isInstalled = false
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
if (!isInstalled) {
|
|
549
|
-
console.log(chalk.yellow("\n⚠️ No se detectó 'opencode' instalado globalmente."))
|
|
550
|
-
console.log(
|
|
549
|
+
console.log(chalk.yellow("\n⚠️ No se detectó 'opencode' instalado globalmente."))
|
|
550
|
+
console.log(
|
|
551
|
+
chalk.gray("OpenCode es el agente CLI recomendado para aprovechar la integración MCP de OPL.")
|
|
552
|
+
)
|
|
551
553
|
const { install } = await inquirer.prompt([
|
|
552
554
|
{
|
|
553
555
|
type: "confirm",
|
|
554
556
|
name: "install",
|
|
555
557
|
message: "¿Deseas instalar opencode globalmente ahora? (npm install -g opencode)",
|
|
556
|
-
default: true
|
|
557
|
-
}
|
|
558
|
-
])
|
|
558
|
+
default: true,
|
|
559
|
+
},
|
|
560
|
+
])
|
|
559
561
|
|
|
560
562
|
if (install) {
|
|
561
|
-
console.log(chalk.cyan("⏳ Instalando opencode..."))
|
|
563
|
+
console.log(chalk.cyan("⏳ Instalando opencode..."))
|
|
562
564
|
try {
|
|
563
|
-
execSync("npm install -g opencode", { stdio: "inherit" })
|
|
564
|
-
console.log(chalk.green("✅ opencode instalado exitosamente.\n"))
|
|
565
|
+
execSync("npm install -g opencode", { stdio: "inherit" })
|
|
566
|
+
console.log(chalk.green("✅ opencode instalado exitosamente.\n"))
|
|
565
567
|
} catch (e) {
|
|
566
|
-
console.log(
|
|
568
|
+
console.log(
|
|
569
|
+
chalk.red(
|
|
570
|
+
"❌ Error instalando opencode. Puedes instalarlo manualmente con: npm install -g opencode\n"
|
|
571
|
+
)
|
|
572
|
+
)
|
|
567
573
|
}
|
|
568
574
|
}
|
|
569
575
|
}
|
|
@@ -224,7 +224,7 @@ async function setupOpencode(cwd, config, langId, index) {
|
|
|
224
224
|
mcp: {
|
|
225
225
|
openPrompt: {
|
|
226
226
|
type: "local",
|
|
227
|
-
command: ["
|
|
227
|
+
command: ["openprompt-lang", "mcp"],
|
|
228
228
|
enabled: true,
|
|
229
229
|
},
|
|
230
230
|
},
|
|
@@ -349,8 +349,8 @@ async function setupVscode(cwd, config, langId) {
|
|
|
349
349
|
servers: {
|
|
350
350
|
"openPrompt-Lang": {
|
|
351
351
|
type: "stdio",
|
|
352
|
-
command: "
|
|
353
|
-
args: ["
|
|
352
|
+
command: "openprompt-lang",
|
|
353
|
+
args: ["mcp"],
|
|
354
354
|
},
|
|
355
355
|
},
|
|
356
356
|
}
|
|
@@ -381,8 +381,8 @@ async function setupAntigravity(cwd, config, langId) {
|
|
|
381
381
|
const globalMcp = {
|
|
382
382
|
mcpServers: {
|
|
383
383
|
"openPrompt-Lang": {
|
|
384
|
-
command: "
|
|
385
|
-
args: ["
|
|
384
|
+
command: "openprompt-lang",
|
|
385
|
+
args: ["mcp"],
|
|
386
386
|
},
|
|
387
387
|
},
|
|
388
388
|
}
|
|
@@ -603,6 +603,15 @@ async function start(description) {
|
|
|
603
603
|
|
|
604
604
|
writeJSON(SESSION_FILE, session)
|
|
605
605
|
appendLog("session_start", description)
|
|
606
|
+
|
|
607
|
+
// Sincronizar con MCP shared state (workflow delivery → modo execute)
|
|
608
|
+
try {
|
|
609
|
+
const { syncWorkflowPhase } = await import("../mcp-shared-state.js")
|
|
610
|
+
syncWorkflowPhase("DELIVERY")
|
|
611
|
+
} catch {
|
|
612
|
+
/* silencioso */
|
|
613
|
+
}
|
|
614
|
+
|
|
606
615
|
console.log(chalk.green(`\n✅ Sesión iniciada: ${session.session.id}\n`))
|
|
607
616
|
console.log(` ${chalk.bold("Tarea:")} ${description}`)
|
|
608
617
|
console.log(` ${chalk.bold("Rama:")} ${branch}`)
|
|
@@ -824,6 +833,14 @@ async function close() {
|
|
|
824
833
|
writeJSON(SESSION_FILE, session)
|
|
825
834
|
appendLog("session_close", `Sesión cerrada: ${session.session.id}`)
|
|
826
835
|
|
|
836
|
+
// Sincronizar con MCP shared state (workflow close → modo plan de nuevo)
|
|
837
|
+
try {
|
|
838
|
+
const { syncWorkflowPhase } = await import("../mcp-shared-state.js")
|
|
839
|
+
syncWorkflowPhase("CLOSE_SESSION")
|
|
840
|
+
} catch {
|
|
841
|
+
/* silencioso */
|
|
842
|
+
}
|
|
843
|
+
|
|
827
844
|
try {
|
|
828
845
|
const { updateProjectContext } = await import("./project-context.js")
|
|
829
846
|
await updateProjectContext(resolveBase())
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "../../../persistence/sqlite/queries.js"
|
|
17
17
|
import { executeTransition, canTransition } from "../../../core/workflow/transitions.js"
|
|
18
18
|
import { generateSessionDocs, generateIndexDoc } from "../../../docgen/session-docs.js"
|
|
19
|
-
import { setPhase } from "../../../mcp-shared-state.js"
|
|
19
|
+
import { setPhase, syncWorkflowPhase } from "../../../mcp-shared-state.js"
|
|
20
20
|
import chalk from "chalk"
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -148,6 +148,7 @@ export async function closeWorkflow(options = {}) {
|
|
|
148
148
|
setMetadata(db, "active_session", "")
|
|
149
149
|
setMetadata(db, "active_ticket", "")
|
|
150
150
|
setPhase("idle", process.cwd())
|
|
151
|
+
syncWorkflowPhase("CLOSE_SESSION")
|
|
151
152
|
|
|
152
153
|
// ─── 3. Generar documentación viva ────────────────────────────────────
|
|
153
154
|
const sessionFiles = generateSessionDocs({
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createTicket,
|
|
14
14
|
createSession,
|
|
15
15
|
} from "../../../persistence/sqlite/queries.js"
|
|
16
|
+
import { syncWorkflowPhase } from "../../../mcp-shared-state.js"
|
|
16
17
|
import { executeTransition, canTransition } from "../../../core/workflow/transitions.js"
|
|
17
18
|
import { getPhase } from "../../../core/workflow/phases.js"
|
|
18
19
|
import chalk from "chalk"
|
|
@@ -133,6 +134,9 @@ export async function delivery(options = {}) {
|
|
|
133
134
|
setMetadata(db, "active_ticket", ticketId)
|
|
134
135
|
setMetadata(db, "workflow_phase", "DELIVERY")
|
|
135
136
|
|
|
137
|
+
// Sincronizar con MCP shared state: DELIVERY → modo execute
|
|
138
|
+
syncWorkflowPhase("DELIVERY")
|
|
139
|
+
|
|
136
140
|
conn.close()
|
|
137
141
|
|
|
138
142
|
const phaseInfo = getPhase("DELIVERY")
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
canTransition,
|
|
13
13
|
getInitialContext,
|
|
14
14
|
} from "../../../core/workflow/transitions.js"
|
|
15
|
+
import { syncWorkflowPhase } from "../../../mcp-shared-state.js"
|
|
15
16
|
import { runWizard, getWizardResponses } from "../../../wizard/orchestrator.js"
|
|
16
17
|
import chalk from "chalk"
|
|
17
18
|
|
|
@@ -75,6 +76,9 @@ export async function discovery(options = {}) {
|
|
|
75
76
|
setMetadata(db, "workflow_project_id", projectId)
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
// Sincronizar con MCP shared state: DISCOVERY → modo plan
|
|
80
|
+
syncWorkflowPhase("DISCOVERY")
|
|
81
|
+
|
|
78
82
|
conn.close()
|
|
79
83
|
|
|
80
84
|
console.log(chalk.green(`\n✅ Discovery completado para: ${projectId}`))
|
|
@@ -8,6 +8,7 @@ import { ensureSchema } from "../../../persistence/sqlite/schema.js"
|
|
|
8
8
|
import { runMigrations } from "../../../persistence/sqlite/migrations.js"
|
|
9
9
|
import { getMetadata, setMetadata } from "../../../persistence/sqlite/queries.js"
|
|
10
10
|
import { executeTransition, canTransition } from "../../../core/workflow/transitions.js"
|
|
11
|
+
import { syncWorkflowPhase } from "../../../mcp-shared-state.js"
|
|
11
12
|
import { getWizardResponses } from "../../../wizard/orchestrator.js"
|
|
12
13
|
import { generateDocs } from "../../../docgen/generator.js"
|
|
13
14
|
import {
|
|
@@ -103,6 +104,9 @@ export async function specification(options = {}) {
|
|
|
103
104
|
executeTransition(ctx.currentPhase, "SPECIFICATION", ctx)
|
|
104
105
|
setMetadata(db, "workflow_phase", "SPECIFICATION")
|
|
105
106
|
|
|
107
|
+
// Sincronizar con MCP shared state: SPECIFICATION → modo plan
|
|
108
|
+
syncWorkflowPhase("SPECIFICATION")
|
|
109
|
+
|
|
106
110
|
conn.close()
|
|
107
111
|
|
|
108
112
|
console.log(chalk.green(`\n✅ Specification completada para: ${projectId}`))
|
|
@@ -89,10 +89,12 @@ async function executeLocalFast(payload, cwd, startAt, startTime) {
|
|
|
89
89
|
const timeout = constraints.timeoutMs || 30000
|
|
90
90
|
let isTimeout = false
|
|
91
91
|
|
|
92
|
+
// shell: false — args ya se dividen manualmente (bin + args).
|
|
93
|
+
// Esto previene command injection si el comando contiene caracteres especiales.
|
|
92
94
|
const child = spawn(bin, args, {
|
|
93
95
|
cwd,
|
|
94
|
-
shell:
|
|
95
|
-
env: process.env,
|
|
96
|
+
shell: false,
|
|
97
|
+
env: process.env,
|
|
96
98
|
})
|
|
97
99
|
|
|
98
100
|
const timeoutId = setTimeout(() => {
|
|
@@ -210,8 +212,10 @@ async function executeDockerEphemeral(payload, cwd, startAt, startTime) {
|
|
|
210
212
|
let stderrRaw = ""
|
|
211
213
|
let isTimeout = false
|
|
212
214
|
|
|
215
|
+
// shell: false — dockerArgs ya se parsean manualmente como array.
|
|
216
|
+
// Los comandos internos de docker se ejecutan via "sh -c '<cmd>'".
|
|
213
217
|
const child = spawn("docker", dockerArgs, {
|
|
214
|
-
shell:
|
|
218
|
+
shell: false,
|
|
215
219
|
})
|
|
216
220
|
|
|
217
221
|
const timeoutId = setTimeout(() => {
|
package/src/mcp-plan-server.js
CHANGED
|
@@ -57,7 +57,8 @@ const TOOLS = [
|
|
|
57
57
|
name: "plan_create",
|
|
58
58
|
description:
|
|
59
59
|
"[PLAN MODE] Crear plan formal @workflow a partir de las respuestas del wizard. " +
|
|
60
|
-
"Genera un archivo .md en .opencode/work-context/PLANS/ con toda la configuración."
|
|
60
|
+
"Genera un archivo .md en .opencode/work-context/PLANS/ con toda la configuración. " +
|
|
61
|
+
"NO cambia a EXECUTE automáticamente — usa plan_validate + plan_switch_mode después.",
|
|
61
62
|
inputSchema: {
|
|
62
63
|
type: "object",
|
|
63
64
|
properties: {
|
|
@@ -453,9 +454,10 @@ async function handlePlanCreate(args) {
|
|
|
453
454
|
})
|
|
454
455
|
|
|
455
456
|
const planContent = loadPlanFile(planId)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
457
|
+
|
|
458
|
+
// Nota: NO se cambia a EXECUTE automáticamente.
|
|
459
|
+
// El flujo correcto es: plan_create → plan_validate → plan_switch_mode
|
|
460
|
+
// Esto asegura que el plan esté completo antes de permitir ejecución.
|
|
459
461
|
|
|
460
462
|
return {
|
|
461
463
|
content: [
|
|
@@ -466,7 +468,12 @@ async function handlePlanCreate(args) {
|
|
|
466
468
|
type: "plan_created",
|
|
467
469
|
plan_id: planId,
|
|
468
470
|
file_path: filePath,
|
|
469
|
-
|
|
471
|
+
mode: "plan",
|
|
472
|
+
message: `✅ Plan "${project_name}" creado. Usa plan_validate para verificar y plan_switch_mode para comenzar la ejecución.`,
|
|
473
|
+
next_steps: [
|
|
474
|
+
`plan_validate plan_id="${planId}"`,
|
|
475
|
+
`plan_switch_mode plan_id="${planId}"`,
|
|
476
|
+
],
|
|
470
477
|
plan_content: planContent,
|
|
471
478
|
},
|
|
472
479
|
null,
|
package/src/mcp-shared-state.js
CHANGED
|
@@ -212,4 +212,29 @@ export function isExecutionTool(toolName) {
|
|
|
212
212
|
return EXECUTION_TOOLS.includes(toolName)
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Sincroniza el modo MCP según la fase del workflow CLI.
|
|
217
|
+
* Conecta src/commands/workflow/ con el sistema MCP.
|
|
218
|
+
*
|
|
219
|
+
* Fases de planificación (DISCOVERY, SPECIFICATION) → mode "plan"
|
|
220
|
+
* Fases de ejecución (DELIVERY, CLOSE_SESSION) → mode "execute"
|
|
221
|
+
*
|
|
222
|
+
* @param {string} workflowPhase - Fase del workflow (DISCOVERY, SPECIFICATION, DELIVERY, CLOSE_SESSION)
|
|
223
|
+
* @param {string} [cwd] - Directorio del proyecto
|
|
224
|
+
*/
|
|
225
|
+
export function syncWorkflowPhase(workflowPhase, cwd) {
|
|
226
|
+
const planPhases = ["DISCOVERY", "SPECIFICATION", "BACKLOG_READY"]
|
|
227
|
+
const executePhases = ["DELIVERY", "CLOSE_SESSION"]
|
|
228
|
+
|
|
229
|
+
if (planPhases.includes(workflowPhase)) {
|
|
230
|
+
const state = getSharedState(cwd)
|
|
231
|
+
// Solo cambiar si no hay un plan aprobado en ejecución
|
|
232
|
+
if (!state.approved_at || state.mode !== "execute") {
|
|
233
|
+
setMode("plan", cwd)
|
|
234
|
+
}
|
|
235
|
+
} else if (executePhases.includes(workflowPhase)) {
|
|
236
|
+
setMode("execute", cwd)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
215
240
|
export { PLAN_TOOLS, EXECUTION_TOOLS }
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { readFileSync, existsSync, rmSync, writeFileSync, mkdirSync } from "fs"
|
|
4
|
-
import { join, dirname } from "path"
|
|
5
|
-
import { fileURLToPath } from "url"
|
|
6
|
-
import { TOOLS } from "./tools.js"
|
|
7
|
-
import { handleToolCall } from "./router.js"
|
|
8
|
-
import { generateRichInstructions } from "../mcp-workflow.js"
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
-
const __dirname = dirname(__filename)
|
|
12
|
-
|
|
13
|
-
function getServerVersion() {
|
|
14
|
-
try {
|
|
15
|
-
const pkg = JSON.parse(readFileSync(join(__dirname, "../..", "package.json"), "utf-8"))
|
|
16
|
-
return pkg.version || "0.9.0"
|
|
17
|
-
} catch {
|
|
18
|
-
return "0.9.0"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const SUPPORTED_PROTOCOL_VERSIONS = ["2024-11-05", "1.0", "1.0.0", "0.1.0"]
|
|
23
|
-
const LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0]
|
|
24
|
-
|
|
25
|
-
const SERVER_INFO = {
|
|
26
|
-
name: "OPL",
|
|
27
|
-
version: getServerVersion(),
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function sendMessage(msg) {
|
|
31
|
-
const data = JSON.stringify(msg)
|
|
32
|
-
process.stdout.write(`${data}\n`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function sendError(id, code, message) {
|
|
36
|
-
sendMessage({ jsonrpc: "2.0", id, error: { code, message } })
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function sendResult(id, result) {
|
|
40
|
-
sendMessage({ jsonrpc: "2.0", id, result })
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function startServer() {
|
|
44
|
-
// PID lock: evitar servidores duplicados
|
|
45
|
-
const PID_PATH = join(process.cwd(), ".opencode", "mcp.pid")
|
|
46
|
-
try {
|
|
47
|
-
if (existsSync(PID_PATH)) {
|
|
48
|
-
const oldPid = readFileSync(PID_PATH, "utf-8").trim()
|
|
49
|
-
try {
|
|
50
|
-
process.kill(parseInt(oldPid), 0)
|
|
51
|
-
console.error("[mcp] ⚠ Ya hay un servidor MCP activo (PID " + oldPid + "). Saliendo.")
|
|
52
|
-
process.exit(1)
|
|
53
|
-
} catch {
|
|
54
|
-
/* PID huérfano, continuar */
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
mkdirSync(dirname(PID_PATH), { recursive: true })
|
|
58
|
-
writeFileSync(PID_PATH, String(process.pid))
|
|
59
|
-
} catch {
|
|
60
|
-
/* fallback: continuar sin PID lock */
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Graceful shutdown: limpiar PID file y conexiones
|
|
64
|
-
function cleanupAndExit() {
|
|
65
|
-
try {
|
|
66
|
-
if (existsSync(PID_PATH)) rmSync(PID_PATH)
|
|
67
|
-
} catch {
|
|
68
|
-
/* ignore */
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
import("../persistence/sqlite/connection.js").then((m) => m.close())
|
|
72
|
-
} catch {
|
|
73
|
-
/* ignore */
|
|
74
|
-
}
|
|
75
|
-
process.exit(0)
|
|
76
|
-
}
|
|
77
|
-
process.on("SIGTERM", cleanupAndExit)
|
|
78
|
-
process.on("SIGINT", cleanupAndExit)
|
|
79
|
-
|
|
80
|
-
let buffer = ""
|
|
81
|
-
|
|
82
|
-
process.stdin.setEncoding("utf-8")
|
|
83
|
-
process.stdin.on("data", async (chunk) => {
|
|
84
|
-
buffer += chunk
|
|
85
|
-
const lines = buffer.split("\n")
|
|
86
|
-
buffer = lines.pop() || ""
|
|
87
|
-
|
|
88
|
-
for (const line of lines) {
|
|
89
|
-
const trimmed = line.trim()
|
|
90
|
-
if (!trimmed) continue
|
|
91
|
-
|
|
92
|
-
let msg
|
|
93
|
-
try {
|
|
94
|
-
msg = JSON.parse(trimmed)
|
|
95
|
-
} catch {
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (msg.jsonrpc !== "2.0") continue
|
|
100
|
-
|
|
101
|
-
const { id, method, params } = msg
|
|
102
|
-
|
|
103
|
-
const isNotification = id === undefined || id === null
|
|
104
|
-
|
|
105
|
-
switch (method) {
|
|
106
|
-
case "initialize": {
|
|
107
|
-
const clientVersion = params?.protocolVersion
|
|
108
|
-
const clientSupportsLatest =
|
|
109
|
-
clientVersion && SUPPORTED_PROTOCOL_VERSIONS.includes(clientVersion)
|
|
110
|
-
const negotiatedVersion = clientSupportsLatest ? clientVersion : LATEST_PROTOCOL_VERSION
|
|
111
|
-
|
|
112
|
-
// Generate rich dynamic instructions based on project context
|
|
113
|
-
const instructions = generateRichInstructions()
|
|
114
|
-
|
|
115
|
-
sendResult(id, {
|
|
116
|
-
protocolVersion: negotiatedVersion,
|
|
117
|
-
capabilities: {
|
|
118
|
-
tools: { listChanged: false },
|
|
119
|
-
logging: {},
|
|
120
|
-
},
|
|
121
|
-
serverInfo: SERVER_INFO,
|
|
122
|
-
instructions,
|
|
123
|
-
})
|
|
124
|
-
break
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
case "notifications/initialized": {
|
|
128
|
-
break
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
case "tools/list": {
|
|
132
|
-
sendResult(id, { tools: TOOLS })
|
|
133
|
-
break
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
case "tools/call": {
|
|
137
|
-
try {
|
|
138
|
-
const toolName = params?.name
|
|
139
|
-
const toolArgs = params?.arguments || {}
|
|
140
|
-
const result = await handleToolCall(toolName, toolArgs)
|
|
141
|
-
sendResult(id, result)
|
|
142
|
-
} catch (err) {
|
|
143
|
-
sendError(id, -32603, err.message)
|
|
144
|
-
}
|
|
145
|
-
break
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
case "ping": {
|
|
149
|
-
sendResult(id, {})
|
|
150
|
-
break
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
default: {
|
|
154
|
-
if (!isNotification) {
|
|
155
|
-
sendError(id, -32601, `Method not found: ${method}`)
|
|
156
|
-
}
|
|
157
|
-
break
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
process.stdin.on("end", () => {
|
|
164
|
-
cleanupAndExit()
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Start if main module
|
|
169
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
170
|
-
startServer()
|
|
171
|
-
}
|