summaryrefslogtreecommitdiff
path: root/src/mcp/lattex.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mcp/lattex.mjs')
-rw-r--r--src/mcp/lattex.mjs65
1 files changed, 59 insertions, 6 deletions
diff --git a/src/mcp/lattex.mjs b/src/mcp/lattex.mjs
index 356122e..861ff40 100644
--- a/src/mcp/lattex.mjs
+++ b/src/mcp/lattex.mjs
@@ -12,7 +12,7 @@ import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js'
-import { readFileSync, readdirSync, statSync } from 'fs'
+import { readFileSync, readdirSync, statSync, writeFileSync, existsSync, unlinkSync } from 'fs'
import { join, relative } from 'path'
import https from 'https'
@@ -277,6 +277,11 @@ function buildOutputUrl(file, data) {
// ── Compile + fetch log helper ──────────────────────────────
async function compileAndFetchLog(projectId, cookie, csrf, pathDocMap, mainFile) {
+ // Flush in-memory OT changes to database so CLSI sees latest content
+ try {
+ await overleafRequest('POST', `/project/${projectId}/flush`, cookie, csrf)
+ } catch {}
+
const body = {
check: 'silent',
draft: false,
@@ -712,11 +717,59 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case 'compile_latex': {
const mainFile = args?.main_file || null
+ const cwd = process.cwd()
+ const requestPath = join(cwd, '.lattex-compile-request')
+ const resultPath = join(cwd, '.lattex-compile-result')
+
+ // Clean up any stale result file
+ try { unlinkSync(resultPath) } catch {}
+
+ // Write compile request for the main process to pick up
+ const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6)
+ writeFileSync(requestPath, JSON.stringify({
+ requestId,
+ mainFile,
+ timestamp: Date.now()
+ }))
+
+ // Poll for result (main process compiles + downloads PDF + updates UI)
+ const timeout = 120000 // 2 minutes max
+ const pollInterval = 500
+ const start = Date.now()
+ let result = null
+
+ while (Date.now() - start < timeout) {
+ await new Promise(r => setTimeout(r, pollInterval))
+ try {
+ if (existsSync(resultPath)) {
+ result = JSON.parse(readFileSync(resultPath, 'utf-8'))
+ unlinkSync(resultPath)
+ break
+ }
+ } catch {}
+ }
- const { status } = await compileAndFetchLog(projectId, cookie, csrf, pathDocMap, mainFile)
+ if (!result) {
+ // Timeout — fall back to direct compile
+ try { unlinkSync(requestPath) } catch {}
+ const { status } = await compileAndFetchLog(projectId, cookie, csrf, pathDocMap, mainFile)
+ lastCompileStatus = status
+ if (status === 'success') {
+ return textResult('Compilation successful (direct, UI may not have updated).')
+ }
+ return textResult(`Compilation failed (status: ${status}). Use get_compile_log for details.`)
+ }
+
+ // Read compile log written by main process (avoids redundant compile API call)
+ lastCompileStatus = result.status || (result.success ? 'success' : 'failure')
+ const logPath = join(cwd, '.lattex-compile-log')
+ try {
+ lastCompileLog = readFileSync(logPath, 'utf-8')
+ } catch {
+ lastCompileLog = null
+ }
- if (status === 'success') {
- // Parse warnings for summary
+ if (result.success) {
if (lastCompileLog) {
const entries = parseCompileLog(lastCompileLog)
const warnings = entries.filter(e => e.level === 'warning')
@@ -735,7 +788,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const errors = entries.filter(e => e.level === 'error')
const warnings = entries.filter(e => e.level === 'warning')
- const summary = [`Compilation failed (status: ${status}).`]
+ const summary = [`Compilation failed (status: ${result.status || 'failure'}).`]
if (errors.length > 0) {
summary.push(`\n${errors.length} error(s):`)
for (const e of errors.slice(0, 10)) {
@@ -752,7 +805,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
return textResult(summary.join('\n'))
}
- return textResult(`Compilation failed with status: ${status}. No log available.`)
+ return textResult(`Compilation failed (status: ${result.status || 'failure'}). No log available.`)
}
case 'get_compile_errors': {