Skip to main content
Glama

Overleaf MCP Server

by mjyoo2
overleaf-mcp-server.js8.72 kB
// Load project configuration const fs = require('fs'); const path = require('path'); let projectsConfig = {}; try { const projectsFile = fs.readFileSync(path.join(__dirname, 'projects.json'), 'utf8'); projectsConfig = JSON.parse(projectsFile); } catch (err) { // Ignore if projects.json file doesn't exist } const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { ListToolsRequestSchema, CallToolRequestSchema, } = require('@modelcontextprotocol/sdk/types.js'); const OverleafGitClient = require('./overleaf-git-client.js'); const server = new Server( { name: 'overleaf-mcp', version: '1.0.0' }, { capabilities: { tools: {} } } ); // Tools list server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'list_files', description: 'List all files in an Overleaf project', inputSchema: { type: 'object', properties: { extension: { type: 'string', description: 'File extension filter (e.g., .tex)', default: '.tex' }, projectName: { type: 'string', description: 'Project name (default, project2, etc.)' }, gitToken: { type: 'string', description: 'Git token (optional, uses env var)' }, projectId: { type: 'string', description: 'Project ID (optional, uses env var)' } }, additionalProperties: false } }, { name: 'read_file', description: 'Read a file from an Overleaf project', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the file' }, projectName: { type: 'string', description: 'Project name (default, project2, etc.)' }, gitToken: { type: 'string', description: 'Git token (optional, uses env var)' }, projectId: { type: 'string', description: 'Project ID (optional, uses env var)' } }, required: ['filePath'], additionalProperties: false } }, { name: 'get_sections', description: 'Get all sections from a LaTeX file', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the LaTeX file' }, projectName: { type: 'string', description: 'Project name (default, project2, etc.)' }, gitToken: { type: 'string', description: 'Git token (optional, uses env var)' }, projectId: { type: 'string', description: 'Project ID (optional, uses env var)' } }, required: ['filePath'], additionalProperties: false } }, { name: 'get_section_content', description: 'Get content of a specific section', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the LaTeX file' }, sectionTitle: { type: 'string', description: 'Title of the section' }, projectName: { type: 'string', description: 'Project name (default, project2, etc.)' }, gitToken: { type: 'string', description: 'Git token (optional, uses env var)' }, projectId: { type: 'string', description: 'Project ID (optional, uses env var)' } }, required: ['filePath', 'sectionTitle'], additionalProperties: false } }, { name: 'status_summary', description: 'Get a summary of the project status using default credentials', inputSchema: { type: 'object', properties: { projectName: { type: 'string', description: 'Project name (default, project2, etc.)' } }, additionalProperties: false } }, { name: 'list_projects', description: 'List all available projects', inputSchema: { type: 'object', properties: {}, additionalProperties: false } } ] }; }); // Tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Get project information function getProjectConfig(projectName) { if (projectName && projectsConfig.projects && projectsConfig.projects[projectName]) { return projectsConfig.projects[projectName]; } // Use 'default' project as fallback if (projectsConfig.projects && projectsConfig.projects.default) { return projectsConfig.projects.default; } throw new Error('No project configuration found. Please set up projects.json with at least a "default" project.'); } const projectConfig = getProjectConfig(args.projectName); const gitToken = args.gitToken || projectConfig.gitToken; const projectId = args.projectId || projectConfig.projectId; if (!gitToken || !projectId) { throw new Error('Git token and project ID are required. Set in projects.json or environment variables.'); } const client = new OverleafGitClient(gitToken, projectId); switch (name) { case 'list_files': const files = await client.listFiles(args.extension || '.tex'); return { content: [{ type: 'text', text: `Files found: ${files.length}\n\n${files.map(f => `• ${f}`).join('\n')}` }] }; case 'read_file': const content = await client.readFile(args.filePath); return { content: [{ type: 'text', text: `File: ${args.filePath}\nSize: ${content.length} characters\n\n${content}` }] }; case 'get_sections': const sections = await client.getSections(args.filePath); const sectionSummary = sections.map((s, i) => `${i + 1}. [${s.type}] ${s.title}\n Content preview: ${s.content.substring(0, 100).replace(/\s+/g, ' ')}...` ).join('\n\n'); return { content: [{ type: 'text', text: `Sections in ${args.filePath} (${sections.length} total):\n\n${sectionSummary}` }] }; case 'get_section_content': const section = await client.getSection(args.filePath, args.sectionTitle); if (!section) { throw new Error(`Section "${args.sectionTitle}" not found`); } return { content: [{ type: 'text', text: `Section: ${section.title}\nType: ${section.type}\nContent length: ${section.content.length} characters\n\n${section.content}` }] }; case 'list_projects': const projectList = Object.entries(projectsConfig.projects || {}).map(([key, project]) => `• ${key}: ${project.name} (${project.projectId})` ); return { content: [{ type: 'text', text: `Available Projects:\n\n${projectList.join('\n') || 'No projects configured in projects.json'}` }] }; case 'status_summary': // Project status summary const allFiles = await client.listFiles('.tex'); const projectName = projectConfig.name || 'Unknown Project'; let summary = `📄 ${projectName} Status Summary\n\n`; summary += `Project ID: ${projectId}\n`; summary += `Total .tex files: ${allFiles.length}\n`; summary += `Files: ${allFiles.join(', ')}\n\n`; if (allFiles.length > 0) { const mainFile = allFiles.find(f => f.includes('main')) || allFiles[0]; const sections = await client.getSections(mainFile); summary += `📋 Structure of ${mainFile}:\n`; summary += `Total sections: ${sections.length}\n\n`; sections.slice(0, 10).forEach((s, i) => { summary += `${i + 1}. [${s.type}] ${s.title}\n`; }); if (sections.length > 10) { summary += `... and ${sections.length - 10} more sections\n`; } } return { content: [{ type: 'text', text: summary }] }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch(() => {});

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mjyoo2/OverleafMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server