#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { GitManager } from './git-manager.js';
import { AuthManager } from './auth-manager.js';
const server = new Server(
{
name: 'overleaf-mcp',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
const gitManager = new GitManager();
const authManager = new AuthManager();
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'clone_project',
description: 'Clone an Overleaf project to a local directory',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The ID of the Overleaf project',
},
localPath: {
type: 'string',
description: 'The local path to clone the project to (absolute path)',
},
email: {
type: 'string',
description: 'Overleaf account email (optional if configured globally)',
},
token: {
type: 'string',
description: 'Overleaf git token (optional if configured globally)',
},
},
required: ['projectId', 'localPath'],
},
},
{
name: 'pull_changes',
description: 'Pull latest changes from Overleaf for a local project',
inputSchema: {
type: 'object',
properties: {
localPath: {
type: 'string',
description: 'The local path of the project',
},
},
required: ['localPath'],
},
},
{
name: 'push_changes',
description: 'Commit and push changes to Overleaf',
inputSchema: {
type: 'object',
properties: {
localPath: {
type: 'string',
description: 'The local path of the project',
},
message: {
type: 'string',
description: 'Commit message',
},
},
required: ['localPath', 'message'],
},
},
{
name: 'get_status',
description: 'Get the status of the local project (git status and diff)',
inputSchema: {
type: 'object',
properties: {
localPath: {
type: 'string',
description: 'The local path of the project',
},
},
required: ['localPath'],
},
},
{
name: 'configure_auth',
description: 'Configure global Overleaf credentials',
inputSchema: {
type: 'object',
properties: {
email: {
type: 'string',
description: 'Overleaf account email',
},
token: {
type: 'string',
description: 'Overleaf git token',
},
},
required: ['email', 'token'],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
switch (request.params.name) {
case 'clone_project': {
const { projectId, localPath, email, token } = request.params.arguments as any;
const result = await gitManager.cloneProject(projectId, localPath, email, token);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
case 'pull_changes': {
const { localPath } = request.params.arguments as any;
const result = await gitManager.pullChanges(localPath);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
case 'push_changes': {
const { localPath, message } = request.params.arguments as any;
const result = await gitManager.pushChanges(localPath, message);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
case 'get_status': {
const { localPath } = request.params.arguments as any;
const result = await gitManager.getStatus(localPath);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
case 'configure_auth': {
const { email, token } = request.params.arguments as any;
await authManager.saveConfig({ email, token });
return {
content: [{ type: 'text', text: 'Successfully configured authentication' }],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
const transport = new StdioServerTransport();
await server.connect(transport);